Python
DeepLearning
Chainer
CNN

ChainerでCNNしたった_v1

改良版 → [ https://qiita.com/nagayosi/items/aa6706f0873e06936248 ]

Chainer-2でCNNを学習〜テストするためのメモ
最近trainerとかあるけど、あれだと学習の途中で自由になんかするとかができないので、あえて1エポックごとの処理を実装
最後の方に書いてある「プログラム全体」をコピペしていけば動きます!
最初の方は詳しく書いていきます。ので、それがもどかしい人は最後の方だけコピペしてください笑

インストール

chainerはpipでインストールするだけです。
GPUの人はcupyもインストール

pip install chainer cupy

CUDA-9.0のときはcupyを下記コマンドでインストールしてください。

pip install cupy-cuda90

詳しくはエラー集を見てください。
Anacondaの人はcudatoolkitもアップデートする。

conda install -c anaconda cudatoolkit

https://anaconda.org/anaconda/cudatoolkit

作業ディレクトリを作る

とりあえず作業ディレクトリをつくりましょう。今回はchainer-workというディレクトリ内で全部作業します。
そして学習画像とテスト画像を集めたら、こんな感じでおきます。

chainer-work --- Images --- Train --- *.jpg
                         |- Test  --- *.jpg 

画像の名前はクラスラベルをつけて下さい。例えば、
img1_0_.jpg, img2_1_.jpg img3_0_.jpg img4_0_.jpg img5_1_.jpg
みたいに_で囲って下さい。
これは「学習するお」の部分で理由を言います。

ネットワーク定義

Chainerはパラメータを持つlayer(ConvとかFCとか)はメンバとして定義し、callでネットワーク全体のlayerを定義するという構造。

chainerではパラメータを持つものはchainer.links、poolingや活性化関数はchainer.functionsにあります。とうことで、まずこの2つをimportします。

import chainer.links as L
import chainer.functions as F

それぞれL,Fとしたら、あとはネットワークモデルを作るだけです!
ネットワークはクラスで作ります。その時、chainer.Chainを継承します。パラメータを持つもの(convolutionやFully-connected)は__init__()で定義し、クラスのインスタンスが作られる時にメモリが確保されます。ここではまだpoolingや活性化関数を定義しません。__init__()内のclass_labelsはクラス数になっていて、つまり最終層の出力層になります。
実際にネットワーク構造を書くのは__call__です。__call__はインスタンス自体をメソッド風に呼ぶ時に呼ばれる関数です。ここではあえてsoftmaxを書いていません(学習でsoftmax cross entropyを使うため。)。

class Mynet(chainer.Chain):

    def __init__(self, class_labels=10):
        super(Mynet, self).__init__()
        with self.init_scope():
            self.conv1_1 = L.Convolution2D(None, 16, ksize=5, pad=2, nobias=True)
            self.conv1_2 = L.Convolution2D(None, 16, ksize=5, pad=2, nobias=True)
            self.conv2_1 = L.Convolution2D(None, 32, ksize=3, pad=1, nobias=True)
            self.conv2_2 = L.Convolution2D(None, 32, ksize=3, pad=1, nobias=True)
            self.fc1 = L.Linear(None, 512, nobias=True)
            self.fc2 = L.Linear(None, class_labels, nobias=True)      

    def __call__(self, x):
        conv1_1 = self.conv1_1(x)
        conv1_1 = F.relu(conv1_1)
        conv1_2 = self.conv1_2(conv1_1)
        conv1_2 = F.relu(conv1_2)
        pool1 = F.max_pooling_2d(conv1_2, ksize=2, stride=2)
        conv2_1 = self.conv2_1(pool1)
        conv2_1 = F.relu(conv2_1)
        conv2_2 = self.conv2_2(conv2_1)
        conv2_2 = F.relu(conv2_2)
        pool2 = F.max_pooling_2d(conv2_2, ksize=2, stride=2)
        fc1 = self.fc1(pool2)
        fc1 = F.relu(fc1)
        fc2 = self.fc2(fc1)
        return fc2

学習させる

ここから学習させていきます。
まず、chainerをimportします。

import chainer

モデルの呼び出し

まず最初に定義したネットワークのインスタンスを作ります。

model = Mynet(class_labels=10)

GPUのセット

そしてGPUを使う時はGPUのデバイス番号を設定します。と同時にmodelのインスタンスをgpuに設定します。CPUでやる人はここは無視して下さい。

chainer.cuda.get_device(0).use()
model.to_gpu()

最適化の設定

そして最適化の手法を設定します。最適化(optimizer)をimportしてモデルをセットするだけです。ここではMomentumSGDを使います。0.01は学習率です。ここでoptimizerに重み減衰(wegith decay)とか諸々を設定できます!!

optimizer = chainer.optimizers.MomentumSGD(0.01)
optimizer.setup(model)
optimizer.add_hook(chainer.optimizer.WeightDecay(5e-4))

学習データセット

次に学習データと評価データを用意します。chainerはデータを4次元テンソルにします。それぞれ、[ミニバッチ、チャネル、高さ、幅]になっています(caffeとかkerasも同じです。)。
ということで、opencvやPILを使って画像を読み込んでいくだけです。ここは「プログラム全体」の「データセットを呼び出す関数」をそのまま使ってもいいと思います。
ここではそれをそのまま使います。それぞれtrainとtestにいれます。それぞれ画像があるディレクトリのパスを指定します。

TRAIN_PATH = 'Images/Train/'
TEST_PATH = 'Images/Test/'
train, _ = load_images(TRAIN_PATH)
test, _ = load_images(TEST_PATH)

これらを元にデータのiterationを作ります。これはミニバッチなどを指定して、学習で使うデータのインデックスを格納したものになります。今回はミニバッチを8にしてみます。
train_iterが学習時の現在のiterationとかを保持したものとなります。

MINIBATCH_SIZE = 8
train_iter = chainer.iterations.SerialIterator(train, MINIBATCH_SIZE)
test_iter = chainer.iterations.SerialIterator(test, MINIBATCH_SIZE, repeat=False, shuffle=False)

学習するお

ここから実際に学習を始めます!!まずconverをimportします。

from chainer.dataset import convert

まず、train_iterから次のミニバッチを受け取ります。

batch = train_iter.next()

これからconvertを使って入力の画像データと教師ラベルを受け取ります。ここでの0はGPUデバイス番号です。

x_array, t_array = convert.concat_examples(batch, 0)

それぞれ、chainer用の変数インスタンスVariableにいれます。

x = chainer.Variable(x_array)
t = chainer.Variable(t_array)

んで、出力をゲットします。これはmodelインスタンスを関数風に呼ぶことで、__call__が呼ばれて、そのまま順伝搬計算(feed-forward)されます。

y = model(x)

ここでのyはまだ出力の値であり、softmaxとかになっていません!!ここでは学習ということで、chaienr.functionsで用意されてるsoftmax_cross_entropyを使って、lossを求めます。

loss_train = F.softmax_cross_entropy(y, t)

このlossを逆伝搬(feed-back)させて、optimizerを更新します。

model.cleargrads()
loss_train.backward()
optimizer.update()

以上で、1iterationが終了しました。これを1000iterationとかする時は,whileで回します。

EPOCH = 100
while train_iter.epoch < EPOCH:
    batch = train_iter.next()
    x_array, t_array = convert.concat_example(batch, GPU)
    x = chainer.Variable(x_array)
    t = chainer.Variable(t_array)

    y = model(x)
    loss_train = F.softmax_cross_entropy(y, t)
    model.cleargrads()
    loss_train.backward()
    optimizer.update()

これで学習が指定したiterationだけ行われますんで、最後に学習したパラメータを保存します。これはserializersを使って、npzファイルというものに保存します。

SAVE_MODEL = 'mynet.npz'

from chainer import serializers
serializers.save_npz(SAVE_MODEL, optimizer)

以上で学習は終わりです!!! おつです(`・ω・´)ゞ

学習モデルでテストするお

ここまでは学習済みモデルを作りましたので、ここからそのモデルを使ってテストしていきます。まずはchainerをインポートします。

import chainer

最初にネットワークモデルのインスタンスをつくります。

model = Mynet(class_labels=10)

そんでGPUを使う人は使うGPU番号をセットします。

chainer.cuda.get_device_from_id(GPU).use()
model.to_gpu()

つぎに学習済みモデルのパラメータをロード(チューニング)します。これにはserializersを使います。

from chainer import serializers
serializers.load_npz(SAVE_MODEL, model)

次にテスト画像を読み込みます。
 
python
test, file_paths = load_images(TEST_PATH, shuffle=False)

あとは画像を入力して出力をゲットするだけです。ということで、for文内処理していきます。まずはx, t = test[index]で画像データと教師ラベルを取得します。

for index in range(len(test)):
    x, t = test[index]

以下全部for内に書いて下さい。
この状態だとxは[チャネル、高さ、幅]の3次元データになっています。がネットワークは[ミニバッチ、チャネル、高さ、幅]の4次元です。なのでxもむりやり4次元にします。

x = x[None, ...]

あとは入力だけです。ネットワークの出力はただのfully-connectedの値なのでsoftmaxします。

y = model(x)
y = y.data
y = chainer.cuda.to_cpu(y)
y = F.softmax(y).data

これで最大確率のインデックスは次式でとれます。

pred_label = y.argmax(axis=1)

最大確率は次式でとれます。

pred_prob = y.max(axis=1)

あとは正解ラベルと比べるなどしてaccuracyを実装できます。
以上です。おつです。

layerのパラメータの可視化

layerのパラメータ(weightとかbias)を見るための関数
”conv2_2”の部分と"W"を変えれば他のもいける
weightを見たければ W、 biasを見たければ B?

def visualize_layer(layer):

    print('visualize inter layer --> {}'.format(layer.name))

    filter_num = layer.out_channels
    line_num = math.ceil(math.sqrt(filter_num))

    plt.figure()

    for i in range(filter_num):

        im = layer.W.data[i, 0]
        plt.subplot(line_num, line_num, i+1)
        plt.axis('off')
        plt.imshow(im, cmap='jet')

    plt.show()

この関数をこう使う

visualize_layer(model.conv2_2)

中間層の可視化

テスト時などで出力結果以外に中間層を見たいときがあるから、その方法がこちら
まずネットワーク定義のclassで次の関数を追加する。これはcallとほぼ同じだが、returnが中間層になってる これを変えれば見たい層を変えられる

def get_inter_layer(self, x):
        conv1_1 = self.conv1_1(x)
        conv1_1 = F.relu(conv1_1)
        conv1_2 = self.conv1_2(conv1_1)
        conv1_2 = F.relu(conv1_2)
        pool1 = F.max_pooling_2d(conv1_2, ksize=2, stride=2)
        conv2_1 = self.conv2_1(pool1)
        conv2_1 = F.relu(conv2_1)
        conv2_2 = self.conv2_2(conv2_1)
        conv2_2 = F.relu(conv2_2)
        pool2 = F.max_pooling_2d(conv2_2, ksize=2, stride=2)
        conv3_1 = self.conv3_1(pool2)
        conv3_1 = F.relu(conv3_1)
        conv3_2 = self.conv3_2(conv3_1)
        conv3_2 = F.relu(conv3_2)
        conv3_3 = self.conv3_3(conv3_2)
        conv3_3 = F.relu(conv3_3)
        pool3 = F.max_pooling_2d(conv3_3, ksize=2, stride=2)
        fc1 = self.fc1(pool3)
        fc1 = F.relu(fc1)
        fc2 = self.fc2(fc1)
        fc2 = F.relu(fc2)
        fc3 = self.fc3(fc2)
        return conv1_2

で、画像を入力してforwardする

inter_layer = model.get_inter_layer(x)

で、visualize_inter_layerで可視化する

def visualize_inter_layer(layer):

    minibatch, filter_num, h, w = layer.shape

    line_num = math.ceil(math.sqrt(filter_num))

    print(' layer shape --> {}'.format(layer.data.shape))

    plt.figure()
    plt.figure(figsize=(15,15))
    plt.subplots_adjust(left=0.001, right=0.999, top=0.999, bottom=0.001, hspace=0.01, wspace=0.01)

    for i in range(filter_num):

        im = layer.data[0, i]
        plt.subplot(line_num, line_num, i+1)
        plt.axis('off')
        plt.imshow(im, cmap='jet')

    plt.show()

使う時はこんな感じで

visualize_inter_layer(inter_layer)

プログラム全体

import

今回使うimportはこちら

import chainer
from chainer.dataset import convert
import chainer.links as L
import chainer.functions as F
from chainer import serializers
from chainer.links.caffe import CaffeFunction

import glob, random, os
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
import time
import _pickle as pickle

ハイパーパラメータ関係

いろんなハイパーパラメータ設定はこちら

## fine-tuning setting
FT_MODEL = 'result/model.npz'

## paths to train image directory and test image one
HOME = os.path.expanduser('~')
TRAIN_PATH = HOME + '/Deeplearning/Images2/Train/'
TEST_PATH = HOME + '/Deeplearning/Images2/Test/'

## model name for save trained, and model name for load testing
SAVE_MODEL = 'result/model2'
LOAD_MODEL = 'result/model2.npz'

## Train hyper-parameters
CLASS = 2
INPUT_WIDTH = 128
INPUT_HEIGHT = 128
MINIBATCH_SIZE = 20
LEARN_RATE = 0.001
EPOCH = 50
GPU = -1

ネットワーク定義

class Mynet(chainer.Chain):

    def __init__(self, class_labels=10):
        super(Mynet, self).__init__()
        with self.init_scope():
            self.conv1_1 = L.Convolution2D(None, 16, ksize=5, pad=2, nobias=True)
            self.conv1_2 = L.Convolution2D(None, 16, ksize=5, pad=2, nobias=True)
            self.conv2_1 = L.Convolution2D(None, 32, ksize=3, pad=1, nobias=True)
            self.conv2_2 = L.Convolution2D(None, 32, ksize=3, pad=1, nobias=True)
            self.fc1 = L.Linear(None, 512, nobias=True)
            self.fc2 = L.Linear(None, class_labels, nobias=True)      

    def __call__(self, x):
        conv1_1 = self.conv1_1(x)
        conv1_1 = F.relu(conv1_1)
        conv1_2 = self.conv1_2(conv1_1)
        conv1_2 = F.relu(conv1_2)
        pool1 = F.max_pooling_2d(conv1_2, ksize=2, stride=2)
        conv2_1 = self.conv2_1(pool1)
        conv2_1 = F.relu(conv2_1)
        conv2_2 = self.conv2_2(conv2_1)
        conv2_2 = F.relu(conv2_2)
        pool2 = F.max_pooling_2d(conv2_2, ksize=2, stride=2)
        fc1 = self.fc1(pool2)
        fc1 = F.relu(fc1)
        fc2 = self.fc2(fc1)
        return fc2

データセットを呼び出す関数

学習データを呼ぶための関数
今回は画像ファイルの名前を"aaa_bbb.jpg"としてaaaがクラスのインデックスにしなければいけません。
つまりイモリのクラスだったら"1_imori.jpg"、ヤモリのクラスだったら"2_yamori.jpg"って感じで。
label = int(filepath.split('/')[-1].split('_')[0])
t = np.array(label, dtype=np.int32)
この部分を変えれば他のパターンでインデックスをつけられます

INPUT_WIDTH = 128
INPUT_HEIGHT = 128

def load_images(dataset_path, shuffle=True):
    filepaths_jpg = glob.glob(dataset_path + '/*.jp*g')
    filepaths_png = glob.glob(dataset_path + '/*.png')
    filepaths = filepaths_jpg + filepaths_png
    filepaths.sort()
    datasets = []
    for filepath in filepaths:
        img = Image.open(filepath).convert('RGB') ## Gray->L, RGB->RGB
        img = img.resize((INPUT_WIDTH, INPUT_HEIGHT))

        x = np.array(img, dtype=np.float32)
        ## Normalize [0, 255] -> [0, 1]
        x = x / 255.
        ## Reshape image to input shape of CNN
        x = x.transpose(2, 0, 1)
        #x = x.reshape(3, INPUT_HEIGHT, INPUT_WIDTH)

        ## Get label(ground-truth) from file name path 
        label = int(filepath.split('/')[-1].split('_')[0])
        t = np.array(label, dtype=np.int32)
        datasets.append((x,t))

    if shuffle: random.shuffle(datasets)

    return datasets, filepaths

Fine-tuningする関数

テスト時に学習済みモデルを読み込むのはserializer.load_npz()とかがある
けど学習時にfine-tuningしたい時に途中までの層のパラメータを読み込む関数がなかった
のでので調べて自分で改造したものがこちら

def fine_tuning(src, dst):
    print("\nFine-tuning start !!")
    assert isinstance(src, chainer.Chain)
    assert isinstance(dst, chainer.Chain)

    for child in src.children():
        if child.name not in dst.__dict__: continue
        dst_child = dst[child.name]
        if type(child) != type(dst_child): continue
        if isinstance(child, chainer.Chain):
            copy_model(child, dst_child)
        if isinstance(child, chainer.Link):
            match = True
            for a, b in zip(child.namedparams(), dst_child.namedparams()):
                if a[0] != b[0]:
                    match = False
                    #break
                if a[1].shape != b[1].shape:
                    match = False
                    #break
                if not match:
                    print('\tIgnore because of parameter mismatch --> %s' % child.name)
                    continue
                for a, b in zip(child.namedparams(), dst_child.namedparams()):
                    b[1].data = a[1].data
                    print('\tfine-tuned from <-- %s' % child.name)

学習のための関数

学習のプログラムの全体はこちら

def main_train(train_Model, ft_Model=None):

    print('\nmodel training start!!\n')
    print('# GPU: {}'.format(GPU))
    print('# Minibatch-size: {}'.format(MINIBATCH_SIZE))
    print('# Epoch: {}'.format(EPOCH))
    print('# Learnrate: {}'.format(LEARN_RATE))

    ## Load train and test images 
    train, _ = load_images(TRAIN_PATH)
    test, _ = load_images(TEST_PATH)

    if len(train) < 1 or len(test) < 1:
        raise Exception('train num : {}, test num: {}'.format(len(train), len(test)))

    train_count = len(train)
    test_count = len(test)

    print('# Train images: {}'.format(train_count))
    print('# Test images: {}\n'.format(test_count))

    ## model define
    model = train_Model

    ## Set GPU device
    if GPU >= 0:
        chainer.cuda.get_device(GPU).use()
        model.to_gpu()

    ## Fine-tuning from pre-trained model
    if ft_Model is not None:
        #orig = pickle.load(open(FT_MODEL, "rb"))

        ## train model parameter initialization
        dummy = np.zeros((3, INPUT_HEIGHT, INPUT_WIDTH))
        dummy = dummy[None, ...]
        dummy = np.array(dummy, dtype=np.float32)
        dummy = chainer.Variable(dummy)
        _, _ = model(dummy)

        ## load fine-tuning original model and copy it to train model
        print('\nFine-tuning from {}\n'.format(FT_MODEL))
        orig = ft_Model
        serializers.load_npz(FT_MODEL, orig)
        fine_tuning(orig, model)

        #serializers.load_npz(FT_MODEL, model)


    ## Set Optimizer
    optimizer = chainer.optimizers.MomentumSGD(LEARN_RATE)
    optimizer.setup(model)
    optimizer.add_hook(chainer.optimizer.WeightDecay(5e-4))

    train_iter = chainer.iterators.SerialIterator(train, MINIBATCH_SIZE)
    test_iter = chainer.iterators.SerialIterator(test, MINIBATCH_SIZE, repeat=False, shuffle=False)

    ## Training start!!
    start = time.time()

    print('epoch  train_loss  train_accuracy  test_loss  test_accuracy  Elapsed-Time')

    while train_iter.epoch < EPOCH:

        batch = train_iter.next()
        # Reduce learning rate by 0.5 every 25 epochs.
        #if train_iter.epoch % 25 == 0 and train_iter.is_new_epoch:
        #    optimizer.lr *= 0.5
        #    print('Reducing learning rate to: ', optimizer.lr)

        train_losses = []
        train_accuracies = []

        x_array, t_array = convert.concat_examples(batch, GPU)
        x = chainer.Variable(x_array)
        t = chainer.Variable(t_array)

        y, _ = model(x)
        loss_train = F.softmax_cross_entropy(y, t)
        accuracy_train = F.accuracy(y, t)
        model.cleargrads()
        loss_train.backward()
        optimizer.update()

        train_losses.append(chainer.cuda.to_cpu(loss_train.data))
        accuracy_train.to_cpu()
        train_accuracies.append(accuracy_train.data)

        if train_iter.is_new_epoch:
            #print('epoch: ', train_iter.epoch)
            #print('train mean loss: {:.2f}, accuracy: {:.2f}'.format( sum_loss_train / train_count, sum_accuracy_train / train_count))
            # evaluation

            test_losses = []
            test_accuracies = []

            sum_accuracy_test = 0
            sum_loss_test = 0
            #model.predictor.train = False
            for batch in test_iter:
                x_array, t_array = convert.concat_examples(batch, GPU)
                x = chainer.Variable(x_array)
                t = chainer.Variable(t_array)

                y = model(x)

                loss_test = F.softmax_cross_entropy(y, t)
                accuracy_test = F.accuracy(y, t)

                test_losses.append(chainer.cuda.to_cpu(loss_test.data))
                accuracy_test.to_cpu()
                test_accuracies.append(accuracy_test.data)


            test_iter.reset()
            #model.predictor.train = True

            print('{:>5}  {:^10.4f}  {:^14.4f}  {:^9.4f}  {:^13.4f}  {:^12.2f}'.format(train_iter.epoch, np.mean(train_losses), np.mean(train_accuracies), np.mean(test_losses), np.mean(test_accuracies), time.time()-start))


    print('\ntraining finished!!\n')

    ## Save the model and the optimizer
    print('save model start!!\n')
    directory = SAVE_MODEL.split('/')[0]
    if not os.path.exists(directory):
        os.system('mkdir {}'.format(directory))
        print('make outout model directory --> {}'.format(directory))

    serializers.save_npz(SAVE_MODEL + '.npz', model)
    print('save the model --> {}'.format(SAVE_MODEL + '.npz') )
    serializers.save_npz(SAVE_MODEL + '.state', optimizer)
    print('save the optimizer --> {}'.format(SAVE_MODEL + '.state'))
    pickle.dump(model, open(SAVE_MODEL + '.pkl', 'wb'))
    print('save the model --> {}'.format(SAVE_MODEL + '.pkl'))

    print('\nmodel save finished!!\n')
使い方
main_train(Train_model=Mynet(CLASS+1), FT_model=Mynet(CLASS))

テストのための関数

テスト時のためのプログラム全体はこちら

def main_test(Model):

    print('\nmodel testing start!!\n')
    model = Model

    if GPU >= 0:
        chainer.cuda.get_device_from_id(GPU).use()
        model.to_gpu()

    serializers.load_npz(LOAD_MODEL, model)
    print('Load from {}'.format(LOAD_MODEL))

    test, filepaths = load_images(TEST_PATH, shuffle=False)

    print('Test images: {}\n'.format(len(test)))
    #image_visualize(test)

    count = 0
    print('{:^20s} : gt / predict (probability)'.format('image-name'))
    print('-----------------------------------------------------------------------')

    for index in range(len(test)):
        x, t = test[index]
        #plt.imshow((x.reshape(INPUT_WIDTH, INPUT_HEIGHT, 3))); plt.show()

        x = x[None, ...]

        #x = chainer.cuda.to_gpu(x, 0)
        y = model(x)
        y = y.data
        y = chainer.cuda.to_cpu(y)
        y = F.softmax(y).data

        pred_label = y.argmax(axis=1)

        print(' {:20s}: {} / {} ({:.3f})'.format(filepaths[index].split('/')[-1], t, pred_label[0], y.max()))

        if t == pred_label:
            count += 1

    print('-----------------------------------------------------------------------')
    print('accuracy: {} ({}/{})'.format(1. * count / len(test), count, len(test)))

    print('\nmodel testing finished!!\n')
使い方
main_test(Mynet(CLASS+1))

エラー集

Traceback (most recent call last):
  File "/home/usrs/nagayosi/dev/anaconda3/lib/python3.6/site-packages/cupy/cuda/compiler.py", line 241, in compile
    nvrtc.compileProgram(self.ptr, options)
  File "cupy/cuda/nvrtc.pyx", line 98, in cupy.cuda.nvrtc.compileProgram
  File "cupy/cuda/nvrtc.pyx", line 108, in cupy.cuda.nvrtc.compileProgram
  File "cupy/cuda/nvrtc.pyx", line 53, in cupy.cuda.nvrtc.check_status
cupy.cuda.nvrtc.NVRTCError: NVRTC_ERROR_COMPILATION (6)

どうやらcupyとcudaのバージョンによるエラーらしい。
私は,Ubuntu16.04, chainer-4.2.0, cupy-4.2.0, CUDA-9.0, CUDNN-7.0.4, Anaconda-5.1.0(Python-3.6.4)で起きた。
普通にpip install cupyすると、CUDA-8.0をターゲットとしてしまうみたいです。
なので、下記コマンドでCUDA-9.0用のcupyをインストールしてください。

pip install cupy-cuda90

cupyに関してはこちらに書いてありました。
https://docs-cupy.chainer.org/en/latest/install.html#install-cupy

Anacondaの人はcudatoolkitもアップデートする。
これをしないとconcat層を使うときに上記のエラーが起こります。

conda install -c anaconda cudatoolkit

https://anaconda.org/anaconda/cudatoolkit