├── .github └── workflows │ ├── ci.yml │ └── document.yml ├── .gitignore ├── .rspec ├── .yardopts ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── bin ├── console └── setup ├── examples ├── cifar │ ├── models │ │ ├── resnet18.rb │ │ └── vgg.rb │ └── train_cifar.rb ├── iris │ └── iris.rb └── mnist │ └── mnist.rb ├── lib ├── chainer.rb └── chainer │ ├── backend.rb │ ├── configuration.rb │ ├── cuda.rb │ ├── dataset │ ├── convert.rb │ └── iterator.rb │ ├── datasets │ ├── cifar.rb │ ├── mnist.rb │ └── tuple_dataset.rb │ ├── device.rb │ ├── function.rb │ ├── function_node.rb │ ├── functions │ ├── activation │ │ ├── leaky_relu.rb │ │ ├── log_softmax.rb │ │ ├── relu.rb │ │ ├── relu_grad2.rb │ │ ├── sigmoid.rb │ │ ├── sigmoid_grad.rb │ │ └── tanh.rb │ ├── array │ │ ├── broadcast_to.rb │ │ ├── cast.rb │ │ ├── reshape.rb │ │ ├── rollaxis.rb │ │ ├── select_item.rb │ │ ├── squeeze.rb │ │ └── transpose.rb │ ├── connection │ │ ├── convolution_2d.rb │ │ ├── convolution_2d_grad_w.rb │ │ ├── deconvolution_2d.rb │ │ ├── embed_id.rb │ │ └── linear.rb │ ├── evaluation │ │ └── accuracy.rb │ ├── loss │ │ ├── mean_squared_error.rb │ │ └── softmax_cross_entropy.rb │ ├── math │ │ ├── basic_math.rb │ │ ├── exp.rb │ │ ├── identity.rb │ │ └── sum.rb │ ├── noise │ │ └── dropout.rb │ ├── normalization │ │ └── batch_normalization.rb │ └── pooling │ │ ├── average_pooling_2d.rb │ │ ├── max_pooling_2d.rb │ │ └── pooling_2d.rb │ ├── gradient_check.rb │ ├── gradient_method.rb │ ├── hyperparameter.rb │ ├── initializer.rb │ ├── initializers │ ├── constant.rb │ ├── init.rb │ ├── normal.rb │ └── uniform.rb │ ├── iterators │ └── serial_iterator.rb │ ├── link.rb │ ├── links │ ├── connection │ │ ├── convolution_2d.rb │ │ ├── embed_id.rb │ │ └── linear.rb │ ├── model │ │ └── classifier.rb │ └── normalization │ │ └── batch_normalization.rb │ ├── optimizer.rb │ ├── optimizers │ ├── adam.rb │ └── momentum_sgd.rb │ ├── parameter.rb │ ├── reporter.rb │ ├── serializer.rb │ ├── serializers │ └── marshal.rb │ ├── testing │ └── array.rb │ ├── training │ ├── extension.rb │ ├── extensions │ │ ├── evaluator.rb │ │ ├── exponential_shift.rb │ │ ├── log_report.rb │ │ ├── print_report.rb │ │ ├── progress_bar.rb │ │ └── snapshot.rb │ ├── standard_updater.rb │ ├── trainer.rb │ ├── triggers │ │ └── interval.rb │ ├── updater.rb │ └── util.rb │ ├── utils │ ├── array.rb │ ├── conv.rb │ ├── initializer.rb │ ├── math.rb │ └── variable.rb │ ├── variable.rb │ ├── variable_node.rb │ └── version.rb ├── red-chainer.gemspec ├── templates └── default │ ├── layout │ └── html │ │ └── layout.erb │ └── onefile │ └── html │ └── layout.erb └── test ├── backend_test.rb ├── dataset └── convert_test.rb ├── device_test.rb ├── function_node_test.rb ├── functions ├── activation │ ├── leaky_relu_test.rb │ ├── log_softmax_test.rb │ ├── relu_test.rb │ ├── sigmoid_test.rb │ └── tanh_test.rb ├── array │ ├── broadcast_to_test.rb │ ├── cast_test.rb │ ├── reshape_test.rb │ ├── rollaxis_test.rb │ ├── select_item_test.rb │ ├── squeeze_test.rb │ └── transpose_test.rb ├── connection │ ├── convolution_2d_test.rb │ ├── deconvolution_2d_test.rb │ └── linear_test.rb ├── loss │ ├── mean_squared_error_test.rb │ └── softmax_cross_entropy_test.rb ├── math │ ├── basic_math_test.rb │ ├── exp_test.rb │ └── sum_test.rb ├── noise │ └── dropout_test.rb ├── normalization │ └── batch_normalization_test.rb └── pooling │ ├── average_pooling_2d_test.rb │ └── max_pooling_2d_test.rb ├── gradient_check_test.rb ├── initializers └── uniform_test.rb ├── links └── connection │ ├── convolution_2d_test.rb │ └── embed_id_test.rb ├── optimizer_test.rb ├── optimizers └── momentum_sgd_test.rb ├── parameter_test.rb ├── run_test.rb ├── training └── extensions │ └── exponential_shift_test.rb ├── utils ├── array_test.rb ├── conv_test.rb └── math_test.rb └── variable_test.rb /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | on: [push, pull_request] 3 | jobs: 4 | build: 5 | name: ${{ matrix.os }} Ruby ${{ matrix.ruby }} 6 | runs-on: ${{ matrix.os }}-latest 7 | strategy: 8 | matrix: 9 | os: ['ubuntu', 'macos'] 10 | ruby: [ '2.6', '2.7', '3.0', '3.1' ] 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: ruby/setup-ruby@v1 14 | with: 15 | ruby-version: ${{ matrix.ruby }} 16 | - run: gem install bundler 17 | - run: bundle install 18 | - run: bundle exec rake test 19 | - run: bundle exec yardoc --fail-on-warning 20 | 21 | -------------------------------------------------------------------------------- /.github/workflows/document.yml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | 18 | name: Document 19 | on: 20 | push: 21 | branches: 22 | - master 23 | tags: 24 | - "*" 25 | jobs: 26 | deploy: 27 | name: Deploy 28 | runs-on: ubuntu-latest 29 | steps: 30 | - uses: actions/checkout@master 31 | - uses: actions/setup-ruby@v1 32 | - name: Install dependencies 33 | run: | 34 | bundle install 35 | - name: Generate document 36 | run: | 37 | bundle exec rake yard 38 | mv doc ../doc 39 | - name: Deploy 40 | run: | 41 | git config user.name "GitHub Actions" 42 | git config user.email "actions@noreply.github.com" 43 | git remote add deploy \ 44 | https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git 45 | git fetch deploy 46 | if ! git checkout --track deploy/gh-pages; then 47 | git checkout --orphan gh-pages 48 | git rm --cached -r . 49 | fi 50 | git clean --force -d -x 51 | rsync \ 52 | -a \ 53 | --delete \ 54 | --exclude "/.git/" \ 55 | ../doc/ \ 56 | ./$(ruby -e 'print ENV["GITHUB_REF"].split("/").last') 57 | git add --all 58 | git commit -m "Update: ${GITHUB_SHA}" 59 | git push deploy gh-pages 60 | env: 61 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 62 | if: | 63 | github.ref 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc/ 3 | /Gemfile.lock 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /tmp/ 9 | result 10 | Gemfile.local 11 | 12 | # rspec failure tracking 13 | .rspec_status 14 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | -p templates 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec 4 | 5 | local_gemfile = File.join(File.dirname(__FILE__), "Gemfile.local") 6 | if File.exist?(local_gemfile) 7 | puts "Loading Gemfile.local ..." if $DEBUG # `ruby -d` or `bundle -v` 8 | instance_eval File.read(local_gemfile) 9 | end 10 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Preferred Infrastructure, Inc. 4 | Copyright (c) 2015 Preferred Networks, Inc. 5 | Copyright (c) 2017 yusaku.hatanaka 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Red Chainer : A deep learning framework 2 | 3 | A flexible framework for neural network for Ruby 4 | 5 | ## Description 6 | 7 | It ported python's [Chainer](https://github.com/chainer/chainer) with Ruby. 8 | 9 | ## Requirements 10 | 11 | * Ruby 2.4 or later 12 | 13 | ## Installation 14 | 15 | Add this line to your application's Gemfile: 16 | 17 | ```bash 18 | gem 'red-chainer' 19 | ``` 20 | 21 | And then execute: 22 | 23 | ```bash 24 | $ bundle 25 | ``` 26 | 27 | Or install it yourself as: 28 | 29 | ```bash 30 | $ gem install red-chainer 31 | ``` 32 | 33 | ## Usage 34 | 35 | ### Run MNIST example 36 | 37 | MNIST sample program is [here](./examples/mnist/mnist.rb) 38 | 39 | ```bash 40 | # when install Gemfile 41 | $ bundle exec ruby examples/mnist/mnist.rb 42 | # when install yourself 43 | $ ruby examples/mnist/mnist.rb 44 | ``` 45 | 46 | ### Run MNIST example with GPU 47 | 48 | On GPU machine, add `gem 'cumo'` on **Gemfile.local** and do `bundle install`. 49 | 50 | Run the example with `--gpu` option whose value indicates GPU device ID such as: 51 | 52 | ``` 53 | $ bundle exec ruby examples/mnist/mnist.rb --gpu 0 54 | ``` 55 | 56 | ## Development 57 | 58 | ### Run tests 59 | 60 | ``` 61 | $ bundle exec ruby test/run_test.rb 62 | ``` 63 | 64 | ### Run tests with Cumo 65 | 66 | On GPU machine, add `gem 'cumo'` on **Gemfile.local** and do `bundle install`. 67 | 68 | Run tests with `RED_CHAINER_GPU` environment variable whose value indicates GPU device ID such as: 69 | 70 | ``` 71 | $ bundle exec env RED_CHAINER_GPU=0 ruby test/run_test.rb 72 | ``` 73 | 74 | ## License 75 | 76 | The MIT license. See [LICENSE.txt](./LICENSE.txt) for details. 77 | 78 | ## Red Chainer implementation status 79 | 80 | | | Chainer 2.0
(Initial ported version) | Red Chainer (0.3.1) | example | 81 | | ---- | ---- | ---- | ---- | 82 | | [activation](https://github.com/red-data-tools/red-chainer/tree/master/lib/chainer/functions/activation) | 15 | 5 | LogSoftmax, ReLU, LeakyReLU, Sigmoid, Tanh | 83 | | [loss](https://github.com/red-data-tools/red-chainer/tree/master/lib/chainer/functions/loss) | 17 | 2 | SoftMax, MeanSquaredError | 84 | | [optimizer](https://github.com/red-data-tools/red-chainer/tree/master/lib/chainer/optimizers) | 9 | 2 | Adam, MomentumSGDRule | 85 | | [connection](https://github.com/red-data-tools/red-chainer/tree/master/lib/chainer/functions/connection) | 12 | 2 | Linear, Convolution2D | 86 | | [pooling](https://github.com/red-data-tools/red-chainer/tree/master/lib/chainer/functions/pooling) | 14 | 3 | Pooling2D, MaxPooling2D, AveragePooling2D | 87 | | [example](https://github.com/red-data-tools/red-chainer/tree/master/examples) | 31 | 3 | MNIST, Iris, CIFAR | 88 | | GPU | use CuPy | use [Cumo](https://github.com/sonots/cumo) || 89 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "yard" 3 | 4 | task default: :test 5 | 6 | desc "Run tests" 7 | task :test do 8 | ruby("test/run_test.rb") 9 | end 10 | 11 | YARD::Rake::YardocTask.new do |task| 12 | end 13 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "chainer" 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require "pry" 11 | # Pry.start 12 | 13 | require "irb" 14 | IRB.start(__FILE__) 15 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /examples/cifar/models/resnet18.rb: -------------------------------------------------------------------------------- 1 | module ResNet18 2 | class Plain < Chainer::Chain 3 | include Chainer::Functions::Activation 4 | include Chainer::Initializers 5 | include Chainer::Links::Connection 6 | include Chainer::Links::Normalization 7 | 8 | def initialize(ch, stride, use_conv: false) 9 | super() 10 | 11 | @use_conv = use_conv 12 | w = HeNormal.new 13 | 14 | init_scope do 15 | @conv1 = Convolution2D.new(nil, ch, 3, stride: stride, pad: 1, nobias: true, initial_w: w) 16 | @bn1 = BatchNormalization.new(ch) 17 | @conv2 = Convolution2D.new(nil, ch, 3, stride: 1, pad: 1, nobias: true, initial_w: w) 18 | @bn2 = BatchNormalization.new(ch) 19 | if @use_conv 20 | @conv3 = Convolution2D.new(nil, ch, 3, stride: stride, pad: 1, nobias: true, initial_w: w) 21 | @bn3 = BatchNormalization.new(ch) 22 | end 23 | end 24 | end 25 | 26 | def call(x) 27 | h = Relu.relu(@bn1.(@conv1.(x))) 28 | h = @bn2.(@conv2.(h)) 29 | if @use_conv 30 | h2 = @bn3.(@conv3.(x)) 31 | Relu.relu(h + h2) 32 | else 33 | Relu.relu(h + x) 34 | end 35 | end 36 | end 37 | 38 | class Block < Chainer::ChainList 39 | def initialize(layer, ch, stride=2) 40 | super() 41 | add_link(Plain.new(ch, stride, use_conv: true)) 42 | (layer-1).times do 43 | add_link(Plain.new(ch, 1)) 44 | end 45 | end 46 | 47 | def call(x) 48 | @children.each do |f| 49 | x = f.(x) 50 | end 51 | x 52 | end 53 | end 54 | 55 | class Model < Chainer::Chain 56 | include Chainer::Functions::Activation 57 | include Chainer::Functions::Evaluation 58 | include Chainer::Functions::Loss 59 | include Chainer::Functions::Pooling 60 | include Chainer::Initializers 61 | include Chainer::Links::Connection 62 | include Chainer::Links::Normalization 63 | 64 | def initialize(n_classes: 10) 65 | super() 66 | initial_w = HeNormal.new 67 | 68 | init_scope do 69 | @conv = Convolution2D.new(3, 64, 7, stride: 2, pad: 3, initial_w: initial_w) 70 | @bn = BatchNormalization.new(64) 71 | 72 | @res2 = Block.new(2, 64, 1) 73 | @res3 = Block.new(2, 128) 74 | @res4 = Block.new(2, 256) 75 | @res5 = Block.new(2, 512) 76 | @fc = Linear.new(nil, out_size: n_classes) 77 | end 78 | end 79 | 80 | def call(x) 81 | h = Relu.relu(@bn.(@conv.(x))) 82 | h = @res2.(h) 83 | h = @res3.(h) 84 | h = @res4.(h) 85 | h = @res5.(h) 86 | h = AveragePooling2D.average_pooling_2d(h, h.shape[2..-1]) 87 | @fc.(h) 88 | end 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /examples/cifar/models/vgg.rb: -------------------------------------------------------------------------------- 1 | class Block < Chainer::Chain 2 | def initialize(out_channels, ksize, pad: 1) 3 | super() 4 | init_scope do 5 | @conv = Chainer::Links::Connection::Convolution2D.new(nil, out_channels, ksize, pad: pad, nobias: true) 6 | @bn = Chainer::Links::Normalization::BatchNormalization.new(out_channels) 7 | end 8 | end 9 | 10 | def call(x) 11 | h = @conv.(x) 12 | h = @bn.(h) 13 | Chainer::Functions::Activation::Relu.relu(h) 14 | end 15 | end 16 | 17 | class VGG < Chainer::Chain 18 | def initialize(n_classes: 10) 19 | super() 20 | init_scope do 21 | @block1_1 = Block.new(64, 3) 22 | @block1_2 = Block.new(64, 3) 23 | @block2_1 = Block.new(128, 3) 24 | @block2_2 = Block.new(128, 3) 25 | @block3_1 = Block.new(256, 3) 26 | @block3_2 = Block.new(256, 3) 27 | @block3_3 = Block.new(256, 3) 28 | @block4_1 = Block.new(512, 3) 29 | @block4_2 = Block.new(512, 3) 30 | @block4_3 = Block.new(512, 3) 31 | @block5_1 = Block.new(512, 3) 32 | @block5_2 = Block.new(512, 3) 33 | @block5_3 = Block.new(512, 3) 34 | @fc1 = Chainer::Links::Connection::Linear.new(nil, out_size: 512, nobias: true) 35 | @bn_fc1 = Chainer::Links::Normalization::BatchNormalization.new(512) 36 | @fc2 = Chainer::Links::Connection::Linear.new(nil, out_size: n_classes, nobias: true) 37 | end 38 | end 39 | 40 | def call(x) 41 | # 64 channel blocks: 42 | h = @block1_1.(x) 43 | h = Chainer::Functions::Noise::Dropout.dropout(h, ratio: 0.3) 44 | h = @block1_2.(h) 45 | h = Chainer::Functions::Pooling::MaxPooling2D.max_pooling_2d(h, 2, stride: 2) 46 | 47 | # 128 channel blocks: 48 | h = @block2_1.(h) 49 | h = Chainer::Functions::Noise::Dropout.dropout(h, ratio: 0.4) 50 | h = @block2_2.(h) 51 | h = Chainer::Functions::Pooling::MaxPooling2D.max_pooling_2d(h, 2, stride:2) 52 | 53 | # 256 channel blocks: 54 | h = @block3_1.(h) 55 | h = Chainer::Functions::Noise::Dropout.dropout(h, ratio: 0.4) 56 | h = @block3_2.(h) 57 | h = Chainer::Functions::Noise::Dropout.dropout(h, ratio: 0.4) 58 | h = @block3_3.(h) 59 | h = Chainer::Functions::Pooling::MaxPooling2D.max_pooling_2d(h, 2, stride: 2) 60 | 61 | # 512 channel blocks: 62 | h = @block4_1.(h) 63 | h = Chainer::Functions::Noise::Dropout.dropout(h, ratio: 0.4) 64 | h = @block4_2.(h) 65 | h = Chainer::Functions::Noise::Dropout.dropout(h, ratio: 0.4) 66 | h = @block4_3.(h) 67 | h = Chainer::Functions::Pooling::MaxPooling2D.max_pooling_2d(h, 2, stride: 2) 68 | 69 | # 512 channel blocks: 70 | h = @block5_1.(h) 71 | h = Chainer::Functions::Noise::Dropout.dropout(h, ratio: 0.4) 72 | h = @block5_2.(h) 73 | h = Chainer::Functions::Noise::Dropout.dropout(h, ratio: 0.4) 74 | h = @block5_3.(h) 75 | h = Chainer::Functions::Pooling::MaxPooling2D.max_pooling_2d(h, 2, stride: 2) 76 | 77 | h = Chainer::Functions::Noise::Dropout.dropout(h, ratio: 0.5) 78 | h = @fc1.(h) 79 | h = @bn_fc1.(h) 80 | h = Chainer::Functions::Activation::Relu.relu(h) 81 | h = Chainer::Functions::Noise::Dropout.dropout(h, ratio: 0.5) 82 | @fc2.(h) 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /examples/cifar/train_cifar.rb: -------------------------------------------------------------------------------- 1 | require 'chainer' 2 | require __dir__ + '/models/vgg' 3 | require __dir__ + '/models/resnet18' 4 | require 'optparse' 5 | 6 | args = { 7 | dataset: 'cifar10', 8 | frequency: -1, 9 | batchsize: 64, 10 | learnrate: 0.05, 11 | epoch: 300, 12 | gpu: Integer(ENV['RED_CHAINER_GPU'] || -1), 13 | out: 'result', 14 | resume: nil, 15 | model: 'vgg', 16 | } 17 | 18 | 19 | opt = OptionParser.new 20 | opt.on('-d', '--dataset VALUE', "The dataset to use: cifar10 or cifar100 (default: #{args[:dataset]})") { |v| args[:dataset] = v } 21 | opt.on('-b', '--batchsize VALUE', "Number of images in each mini-batch (default: #{args[:batchsize]})") { |v| args[:batchsize] = v.to_i } 22 | opt.on('-f', '--frequency VALUE', "Frequency of taking a snapshot (default: #{args[:frequency]})") { |v| args[:frequency] = v.to_i } 23 | opt.on('-l', '--learnrate VALUE', "Learning rate for SGD (default: #{args[:learnrate]})") { |v| args[:learnrate] = v.to_f } 24 | opt.on('-e', '--epoch VALUE', "Number of sweeps over the dataset to train (default: #{args[:epoch]})") { |v| args[:epoch] = v.to_i } 25 | opt.on('-g', '--gpu VALUE', "GPU ID (negative value indicates CPU) (default: #{args[:gpu]})") { |v| args[:gpu] = v.to_i } 26 | opt.on('-o', '--out VALUE', "Directory to output the result (default: #{args[:out]})") { |v| args[:out] = v } 27 | opt.on('-r', '--resume VALUE', "Resume the training from snapshot") { |v| args[:resume] = v } 28 | opt.on('-m', '--model VALUE', "Use model") { |v| args[:model] = v } 29 | opt.parse!(ARGV) 30 | 31 | puts "GPU: #{args[:gpu]}" 32 | puts "# unit: #{args[:unit]}" 33 | puts "# Minibatch-size: #{args[:batchsize]}" 34 | puts "# epoch: #{args[:epoch]}" 35 | puts 36 | 37 | device = Chainer::Device.create(args[:gpu]) 38 | Chainer::Device.change_default(device) 39 | 40 | # Set up a neural network to train. 41 | # Classifier reports softmax cross entropy loss and accuracy at every 42 | # iteration, which will be used by the PrintReport extension below. 43 | if args[:dataset] == 'cifar10' 44 | puts 'Using CIFAR10 dataset.' 45 | class_labels = 10 46 | train, test = Chainer::Datasets::CIFAR.get_cifar10 47 | elsif args[:dataset] == 'cifar100' 48 | puts 'Using CIFAR100 dataset.' 49 | class_labels = 100 50 | train, test = Chainer::Datasets::CIFAR.get_cifar100 51 | else 52 | raise 'Invalid dataset choice.' 53 | end 54 | 55 | if args[:model] == 'vgg' 56 | puts 'Using VGG model' 57 | model_class = VGG 58 | elsif args[:model] == 'resnet18' 59 | puts 'Using ResNet-18 model' 60 | model_class = ResNet18::Model 61 | else 62 | raise "model must be 'vgg' or 'resnet18', but was #{args[:model]}" 63 | end 64 | 65 | model = Chainer::Links::Model::Classifier.new(model_class.new(n_classes: class_labels)) 66 | 67 | optimizer = Chainer::Optimizers::MomentumSGD.new(lr: args[:learnrate]) 68 | optimizer.setup(model) 69 | 70 | train_iter = Chainer::Iterators::SerialIterator.new(train, args[:batchsize]) 71 | test_iter = Chainer::Iterators::SerialIterator.new(test, args[:batchsize], repeat: false, shuffle: false) 72 | 73 | updater = Chainer::Training::StandardUpdater.new(train_iter, optimizer, device: device) 74 | trainer = Chainer::Training::Trainer.new(updater, stop_trigger: [args[:epoch], 'epoch'], out: args[:out]) 75 | 76 | trainer.extend(Chainer::Training::Extensions::Evaluator.new(test_iter, model, device: device)) 77 | 78 | trainer.extend(Chainer::Training::Extensions::ExponentialShift.new('lr', 0.5), trigger: [25, 'epoch']) 79 | 80 | frequency = args[:frequency] == -1 ? args[:epoch] : [1, args[:frequency]].max 81 | trainer.extend(Chainer::Training::Extensions::Snapshot.new, trigger: [frequency, 'epoch']) 82 | 83 | trainer.extend(Chainer::Training::Extensions::LogReport.new) 84 | trainer.extend(Chainer::Training::Extensions::PrintReport.new(['epoch', 'main/loss', 'validation/main/loss', 'main/accuracy', 'validation/main/accuracy', 'elapsed_time'])) 85 | trainer.extend(Chainer::Training::Extensions::ProgressBar.new) 86 | 87 | if args[:resume] 88 | Chainer::Serializers::MarshalDeserializer.load_file(args[:resume], trainer) 89 | end 90 | 91 | trainer.run 92 | 93 | -------------------------------------------------------------------------------- /examples/iris/iris.rb: -------------------------------------------------------------------------------- 1 | require 'numo/narray' 2 | require 'chainer' 3 | require "datasets" 4 | 5 | class IrisChain < Chainer::Chain 6 | L = Chainer::Links::Connection::Linear 7 | F = Chainer::Functions 8 | 9 | def initialize(n_units, n_out) 10 | super() 11 | init_scope do 12 | @l1 = L.new(nil, out_size: n_units) 13 | @l2 = L.new(nil, out_size: n_out) 14 | end 15 | end 16 | 17 | def call(x, y) 18 | return F::Loss::MeanSquaredError.mean_squared_error(fwd(x), y) 19 | end 20 | 21 | def fwd(x) 22 | h1 = F::Activation::Sigmoid.sigmoid(@l1.(x)) 23 | h2 = @l2.(h1) 24 | return h2 25 | end 26 | end 27 | 28 | device = Chainer::Device.create(Integer(ENV['RED_CHAINER_GPU'] || -1)) 29 | Chainer::Device.change_default(device) 30 | xm = device.xm 31 | 32 | model = IrisChain.new(6,3) 33 | 34 | optimizer = Chainer::Optimizers::Adam.new 35 | optimizer.setup(model) 36 | 37 | iris = Datasets::Iris.new 38 | iris_table = iris.to_table 39 | x = iris_table.fetch_values(:sepal_length, :sepal_width, :petal_length, :petal_width).transpose 40 | 41 | # target 42 | y_class = iris_table[:label] 43 | 44 | # class index array 45 | # ["Iris-setosa", "Iris-versicolor", "Iris-virginica"] 46 | class_name = y_class.uniq 47 | # y => [0, 0, 0, 0, ,,, 1, 1, ,,, ,2, 2] 48 | y = y_class.map{|s| 49 | class_name.index(s) 50 | } 51 | 52 | # y_onehot => One-hot [[1.0, 0.0, 0.0], [1.0, 0.0, 0.0],,, [0.0, 1.0, 0.0], ,, [0.0, 0.0, 1.0]] 53 | y_onehot = xm::SFloat.eye(class_name.size)[y, false] 54 | 55 | puts "Iris Datasets" 56 | puts "No. [sepal_length, sepal_width, petal_length, petal_width] one-hot #=> class" 57 | x.each_with_index{|r, i| 58 | puts "#{'%3d' % i} : [#{r.join(', ')}] #{y_onehot[i, false].to_a} #=> #{y_class[i]}(#{y[i]})" 59 | } 60 | # [5.1, 3.5, 1.4, 0.2, "Iris-setosa"] => 50 data 61 | # [7.0, 3.2, 4.7, 1.4, "Iris-versicolor"] => 50 data 62 | # [6.3, 3.3, 6.0, 2.5, "Iris-virginica"] => 50 data 63 | 64 | x = xm::SFloat.cast(x) 65 | y = xm::SFloat.cast(y) 66 | y_onehot = xm::SFloat.cast(y_onehot) 67 | 68 | x_train = x[(1..-1).step(2), true] #=> 75 data (Iris-setosa : 25, Iris-versicolor : 25, Iris-virginica : 25) 69 | y_train = y_onehot[(1..-1).step(2), true] #=> 75 data (Iris-setosa : 25, Iris-versicolor : 25, Iris-virginica : 25) 70 | x_test = x[(0..-1).step(2), true] #=> 75 data (Iris-setosa : 25, Iris-versicolor : 25, Iris-virginica : 25) 71 | y_test = y[(0..-1).step(2)] #=> 75 data (Iris-setosa : 25, Iris-versicolor : 25, Iris-virginica : 25) 72 | 73 | puts 74 | 75 | # Train 76 | print("Training ") 77 | 78 | 10000.times{|i| 79 | print(".") if i % 1000 == 0 80 | x = Chainer::Variable.new(x_train) 81 | y = Chainer::Variable.new(y_train) 82 | model.cleargrads() 83 | loss = model.(x, y) 84 | loss.backward() 85 | optimizer.update() 86 | } 87 | 88 | puts 89 | 90 | # Test 91 | xt = Chainer::Variable.new(x_test) 92 | yt = model.fwd(xt) 93 | n_row, n_col = yt.data.shape 94 | 95 | puts "Result : Correct Answer : Answer <= One-Hot" 96 | ok = 0 97 | n_row.times{|i| 98 | ans = yt.data[i, true].max_index() 99 | if ans == y_test[i] 100 | ok += 1 101 | printf("OK") 102 | else 103 | printf("--") 104 | end 105 | printf(" : #{y_test[i].to_i} :") 106 | 107 | puts " #{ans.to_i} <= #{yt.data[i, 0..-1].to_a}" 108 | } 109 | puts "Row: #{n_row}, Column: #{n_col}" 110 | puts "Accuracy rate : #{ok}/#{n_row} = #{ok.to_f / n_row}" 111 | -------------------------------------------------------------------------------- /examples/mnist/mnist.rb: -------------------------------------------------------------------------------- 1 | require 'chainer' 2 | require 'fileutils' 3 | require 'optparse' 4 | require 'tmpdir' 5 | 6 | class MLP < Chainer::Chain 7 | L = Chainer::Links::Connection::Linear 8 | R = Chainer::Functions::Activation::Relu 9 | 10 | def initialize(n_units, n_out) 11 | super() 12 | init_scope do 13 | @l1 = L.new(nil, out_size: n_units) 14 | @l2 = L.new(nil, out_size: n_units) 15 | @l3 = L.new(nil, out_size: n_out) 16 | end 17 | end 18 | 19 | def call(x) 20 | h1 = R.relu(@l1.(x)) 21 | h2 = R.relu(@l2.(h1)) 22 | @l3.(h2) 23 | end 24 | end 25 | 26 | args = { 27 | batchsize: 100, 28 | frequency: -1, 29 | epoch: 20, 30 | gpu: Integer(ENV['RED_CHAINER_GPU'] || -1), 31 | resume: nil, 32 | unit: 1000, 33 | out: 'result' 34 | } 35 | 36 | opt = OptionParser.new 37 | opt.on('-b', '--batchsize VALUE', "Number of images in each mini-batch (default: #{args[:batchsize]})") { |v| args[:batchsize] = v.to_i } 38 | opt.on('-e', '--epoch VALUE', "Number of sweeps over the dataset to train (default: #{args[:epoch]})") { |v| args[:epoch] = v.to_i } 39 | opt.on('-g', '--gpu VALUE', "GPU ID (negative value indicates CPU) (default: #{args[:gpu]})") { |v| args[:gpu] = v.to_i } 40 | opt.on('-f', '--frequency VALUE', "Frequency of taking a snapshot (default: #{args[:frequency]})") { |v| args[:frequency] = v.to_i } 41 | opt.on('-o', '--out VALUE', "Directory to output the result (default: #{args[:out]})") { |v| args[:out] = v } 42 | opt.on('-r', '--resume VALUE', "Resume the training from snapshot") { |v| args[:resume] = v } 43 | opt.on('-u', '--unit VALUE', "Number of units (default: #{args[:unit]})") { |v| args[:unit] = v.to_i } 44 | opt.parse!(ARGV) 45 | 46 | puts "GPU: #{args[:gpu]}" 47 | puts "# unit: #{args[:unit]}" 48 | puts "# Minibatch-size: #{args[:batchsize]}" 49 | puts "# epoch: #{args[:epoch]}" 50 | puts 51 | 52 | device = Chainer::Device.create(args[:gpu]) 53 | Chainer::Device.change_default(device) 54 | 55 | lossfun = -> (x, t) { Chainer::Functions::Loss::SoftmaxCrossEntropy.new(ignore_label: nil).(x, t) } 56 | model = Chainer::Links::Model::Classifier.new(MLP.new(args[:unit], 10), lossfun) 57 | 58 | optimizer = Chainer::Optimizers::Adam.new 59 | optimizer.setup(model) 60 | train, test = Chainer::Datasets::MNIST.get_mnist 61 | 62 | train_iter = Chainer::Iterators::SerialIterator.new(train, args[:batchsize]) 63 | test_iter = Chainer::Iterators::SerialIterator.new(test, args[:batchsize], repeat: false, shuffle: false) 64 | 65 | updater = Chainer::Training::StandardUpdater.new(train_iter, optimizer, device: device) 66 | trainer = Chainer::Training::Trainer.new(updater, stop_trigger: [args[:epoch], 'epoch'], out: args[:out]) 67 | 68 | trainer.extend(Chainer::Training::Extensions::Evaluator.new(test_iter, model, device: args[:gpu])) 69 | 70 | # Take a snapshot for each specified epoch 71 | frequency = args[:frequency] == -1 ? args[:epoch] : [1, args[:frequency]].max 72 | trainer.extend(Chainer::Training::Extensions::Snapshot.new, trigger: [frequency, 'epoch'], priority: -100) 73 | 74 | trainer.extend(Chainer::Training::Extensions::LogReport.new) 75 | trainer.extend(Chainer::Training::Extensions::PrintReport.new(['epoch', 'main/loss', 'validation/main/loss', 'main/accuracy', 'validation/main/accuracy', 'elapsed_time'])) 76 | trainer.extend(Chainer::Training::Extensions::ProgressBar.new) 77 | 78 | if args[:resume] 79 | Chainer::Serializers::MarshalDeserializer.load_file(args[:resume], trainer) 80 | end 81 | 82 | trainer.run 83 | -------------------------------------------------------------------------------- /lib/chainer.rb: -------------------------------------------------------------------------------- 1 | require "weakref" 2 | 3 | require "chainer/version" 4 | 5 | require 'chainer/cuda' 6 | require 'chainer/backend' 7 | require 'chainer/configuration' 8 | require 'chainer/device' 9 | require 'chainer/function' 10 | require 'chainer/function_node' 11 | require 'chainer/optimizer' 12 | require 'chainer/gradient_method' 13 | require 'chainer/gradient_check' 14 | require 'chainer/hyperparameter' 15 | require 'chainer/dataset/iterator' 16 | require 'chainer/dataset/convert' 17 | require 'chainer/initializer' 18 | require 'chainer/initializers/init' 19 | require 'chainer/initializers/constant' 20 | require 'chainer/initializers/normal' 21 | require 'chainer/initializers/uniform' 22 | require 'chainer/iterators/serial_iterator' 23 | require 'chainer/link' 24 | require 'chainer/links/connection/convolution_2d' 25 | require 'chainer/links/connection/embed_id' 26 | require 'chainer/links/connection/linear' 27 | require 'chainer/links/normalization/batch_normalization' 28 | require 'chainer/links/model/classifier' 29 | require 'chainer/variable' 30 | require 'chainer/variable_node' 31 | require 'chainer/utils/conv' 32 | require 'chainer/utils/initializer' 33 | require 'chainer/utils/math' 34 | require 'chainer/utils/variable' 35 | require 'chainer/utils/array' 36 | require 'chainer/functions/activation/leaky_relu' 37 | require 'chainer/functions/activation/relu' 38 | require 'chainer/functions/activation/relu_grad2' 39 | require 'chainer/functions/activation/sigmoid' 40 | require 'chainer/functions/activation/sigmoid_grad' 41 | require 'chainer/functions/activation/tanh' 42 | require 'chainer/functions/activation/log_softmax' 43 | require 'chainer/functions/array/broadcast_to' 44 | require 'chainer/functions/array/cast' 45 | require 'chainer/functions/array/reshape' 46 | require 'chainer/functions/array/rollaxis' 47 | require 'chainer/functions/array/select_item' 48 | require 'chainer/functions/array/squeeze' 49 | require 'chainer/functions/array/transpose' 50 | require 'chainer/functions/evaluation/accuracy' 51 | require 'chainer/functions/math/basic_math' 52 | require 'chainer/functions/math/identity' 53 | require 'chainer/functions/math/sum' 54 | require 'chainer/functions/math/exp' 55 | require 'chainer/functions/loss/mean_squared_error' 56 | require 'chainer/functions/loss/softmax_cross_entropy' 57 | require 'chainer/functions/connection/convolution_2d' 58 | require 'chainer/functions/connection/deconvolution_2d' 59 | require 'chainer/functions/connection/convolution_2d_grad_w' 60 | require 'chainer/functions/connection/embed_id' 61 | require 'chainer/functions/connection/linear' 62 | require 'chainer/functions/noise/dropout' 63 | require 'chainer/functions/normalization/batch_normalization' 64 | require 'chainer/functions/pooling/pooling_2d' 65 | require 'chainer/functions/pooling/average_pooling_2d' 66 | require 'chainer/functions/pooling/max_pooling_2d' 67 | require 'chainer/testing/array' 68 | require 'chainer/training/extension' 69 | require 'chainer/training/extensions/evaluator' 70 | require 'chainer/training/extensions/exponential_shift' 71 | require 'chainer/training/extensions/log_report' 72 | require 'chainer/training/extensions/print_report' 73 | require 'chainer/training/extensions/progress_bar' 74 | require 'chainer/training/extensions/snapshot' 75 | require 'chainer/training/trainer' 76 | require 'chainer/training/updater' 77 | require 'chainer/training/util' 78 | require 'chainer/training/standard_updater' 79 | require 'chainer/training/triggers/interval' 80 | require 'chainer/parameter' 81 | require 'chainer/optimizers/adam' 82 | require 'chainer/optimizers/momentum_sgd' 83 | require 'chainer/datasets/mnist' 84 | require 'chainer/datasets/cifar' 85 | require 'chainer/datasets/tuple_dataset' 86 | require 'chainer/reporter' 87 | require 'chainer/serializer' 88 | require 'chainer/serializers/marshal' 89 | 90 | require 'numo/narray' 91 | 92 | module Chainer 93 | def self.configure 94 | yield(configuration) 95 | end 96 | 97 | def self.configuration 98 | @configuration ||= Configuration.new 99 | end 100 | end 101 | 102 | -------------------------------------------------------------------------------- /lib/chainer/backend.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | # Gets an appropriate one from +Numo::NArray+ or +Cumo::NArray+ from given arrays. 3 | # 4 | # @param [Array or Array or Array] args Values to determine whether Numo or Cumo should be used. 5 | # @return [Class] +Cumo::NArray+ or +Numo::NArray+ is returned based on the types of the arguments. 6 | def get_array_module(*args) 7 | arrays = args.map {|v| v.kind_of?(Chainer::Variable) ? v.data : v } 8 | if CUDA.available? 9 | return Cumo if arrays.any? {|a| a.kind_of?(Cumo::NArray) } 10 | end 11 | return Numo 12 | end 13 | module_function :get_array_module 14 | 15 | # Returns true if the argument is either of +Numo::NArray+ or +Cumo::NArray+. 16 | # 17 | # @param [Object] obj 18 | # @return [Boolean] 19 | def array?(obj) 20 | if CUDA.available? 21 | return true if obj.kind_of?(Cumo::NArray) 22 | end 23 | return true if obj.kind_of?(Numo::NArray) 24 | false 25 | end 26 | module_function :array? 27 | end 28 | -------------------------------------------------------------------------------- /lib/chainer/configuration.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | class Configuration 3 | attr_accessor :enable_backprop, :train 4 | 5 | def initialize 6 | @enable_backprop = true 7 | @train = true 8 | end 9 | end 10 | end 11 | 12 | -------------------------------------------------------------------------------- /lib/chainer/cuda.rb: -------------------------------------------------------------------------------- 1 | begin 2 | require 'cumo' 3 | $chainer_cuda_available = true 4 | rescue LoadError => e 5 | $chainer_cuda_available = false 6 | # A trick to make Cumo::NArray always exists 7 | module Cumo 8 | class NArray; end 9 | class NMath; end 10 | class Bit; end 11 | end 12 | end 13 | 14 | module Chainer 15 | module CUDA 16 | # Returns whether CUDA is available. 17 | # 18 | # @param [Integer or nil] id If a non negative integer is given, check availability of GPU ID. 19 | # @return [Boolean] 20 | def available?(id = nil) 21 | return false unless $chainer_cuda_available 22 | if id 23 | raise 'id must be non negative' if id < 0 24 | @device_count ||= Cumo::CUDA::Runtime.cudaGetDeviceCount 25 | return @device_count > id 26 | end 27 | true 28 | end 29 | module_function :available? 30 | 31 | # Checks if CUDA is available. 32 | # 33 | # @param [Integer or nil] id If a non negative integer is given, check availability of GPU ID. 34 | # @raise [RuntimeError] if not available 35 | def check_available(id = nil) 36 | raise 'CUDA is not available' unless available?(id) 37 | end 38 | module_function :check_available 39 | 40 | # Returns whether cuDNN is available and enabled. 41 | # 42 | # To disable cuDNN feature, set an environment variable `RED_CHAINER_CUDNN` to 0. 43 | # 44 | # @return [Boolean] 45 | def cudnn_enabled? 46 | return @cudnn_enabled unless @cudnn_enabled.nil? 47 | f = -> () do 48 | return false unless $chainer_cuda_available 49 | return false if Integer(ENV.fetch('RED_CHAINER_CUDNN', '1')) == 0 50 | Cumo::CUDA.const_defined?(:CUDNN) && Cumo::CUDA::CUDNN.available? 51 | end 52 | @cudnn_enabled = f.call 53 | end 54 | module_function :cudnn_enabled? 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/chainer/dataset/convert.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Dataset 3 | module Convert 4 | def self.to_device(device, x) 5 | # TODO(sonots): Implement after Cumo supports transferring between devices 6 | x 7 | end 8 | 9 | def self.concat_examples(batch, device: nil, padding: nil) 10 | raise "batch is empty" if batch.size == 0 11 | device = device ? Chainer::Device.create(device) : Chainer::Device.default # takes care of int and nil 12 | first_elem = batch[0] 13 | 14 | if first_elem.kind_of?(Array) 15 | result = [] 16 | unless padding.kind_of?(Array) 17 | padding = [padding] * first_elem.size 18 | end 19 | 20 | first_elem.size.times do |i| 21 | x = _concat_arrays(batch.map { |b| b[i] }, padding[i], device) 22 | result.push(to_device(device, x)) 23 | end 24 | 25 | return result 26 | else 27 | return _concat_arrays(batch, padding, device) 28 | end 29 | end 30 | 31 | def self._concat_arrays(arrays, padding, device) 32 | xm = device.xm 33 | unless arrays[0].kind_of?(xm::NArray) 34 | # [1, 2, 3, 4] => Numo::Int32[1, 2, 3, 4] 35 | arrays = xm::NArray.cast(arrays) 36 | if padding 37 | return _concat_arrays_with_padding(arrays, padding, device) 38 | end 39 | return arrays 40 | end 41 | 42 | if padding 43 | return _concat_arrays_with_padding(arrays, padding, device) 44 | end 45 | 46 | # [Numo::SFloat[1, 2], Numo::SFloat[3, 4]] 47 | # => Numo::SFloat#shape=[2,2] 48 | # [[1, 2], [3, 4]] 49 | a = arrays.map{|arr| arr[:-, false]} 50 | a[0].concatenate(*a[1..-1]) 51 | end 52 | 53 | def self._concat_arrays_with_padding(arrays, padding, device) 54 | xm = device.xm 55 | if Chainer.array?(arrays[0]) and arrays[0].ndim > 0 56 | xm = Chainer.get_array_module(arrays[0]) 57 | shape = xm::Int32.cast(arrays[0].shape) 58 | arrays[1..-1].each do |array| 59 | if xm::Bit.[](shape != array.shape).any? 60 | shape = xm::Int32.maximum(shape, array.shape) 61 | end 62 | end 63 | else # Integer 64 | shape = [] 65 | end 66 | 67 | shape = shape.insert(0, arrays.size).to_a 68 | if Chainer.array?(arrays[0]) and arrays[0].ndim > 0 69 | result = arrays[0].class.new(shape).fill(padding) 70 | else # Integer 71 | result = xm::Int32.new(shape).fill(padding) 72 | end 73 | 74 | arrays.size.times do |i| 75 | src = arrays[i] 76 | if Chainer.array?(src) and src.ndim > 0 77 | result[i, 0...src.shape[0], 0...src.shape[1]] = src 78 | else # Integer 79 | result[i] = src 80 | end 81 | end 82 | 83 | result 84 | end 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /lib/chainer/dataset/iterator.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Dataset 3 | class Iterator 4 | def next 5 | raise NotImplementedError 6 | end 7 | 8 | def finalize 9 | end 10 | 11 | def serialize(serializer) 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/chainer/datasets/cifar.rb: -------------------------------------------------------------------------------- 1 | require 'datasets' 2 | 3 | module Chainer 4 | module Datasets 5 | module CIFAR 6 | def self.get_cifar10(with_label: true, ndim: 3, scale: 1.0) 7 | get_cifar(10, with_label, ndim, scale) 8 | end 9 | 10 | def self.get_cifar100(with_label: true, ndim: 3, scale: 1.0) 11 | get_cifar(100, with_label, ndim, scale) 12 | end 13 | 14 | def self.get_cifar(n_classes, with_label, ndim, scale, device: Chainer::Device.default) 15 | train_table = ::Datasets::CIFAR.new(n_classes: n_classes, type: :train).to_table 16 | test_table = ::Datasets::CIFAR.new(n_classes: n_classes, type: :test).to_table 17 | 18 | train_data = train_table[:pixels] 19 | test_data = test_table[:pixels] 20 | if n_classes == 10 21 | train_labels = train_table[:label] 22 | test_labels = test_table[:label] 23 | else 24 | train_labels = train_table[:fine_label] 25 | test_labels = test_table[:fine_label] 26 | end 27 | 28 | xm = device.xm 29 | [ 30 | preprocess_cifar(xm::UInt8[*train_data], xm::UInt8[*train_labels], with_label, ndim, scale), 31 | preprocess_cifar(xm::UInt8[*test_data], xm::UInt8[*test_labels], with_label, ndim, scale) 32 | ] 33 | end 34 | 35 | def self.preprocess_cifar(images, labels, withlabel, ndim, scale, device: Chainer::Device.default) 36 | if ndim == 1 37 | images = images.reshape(images.shape[0], 3072) 38 | elsif ndim == 3 39 | images = images.reshape(images.shape[0], 3, 32, 32) 40 | else 41 | raise 'invalid ndim for CIFAR dataset' 42 | end 43 | xm = device.xm 44 | images = images.cast_to(xm::SFloat) 45 | images *= scale / 255.0 46 | 47 | if withlabel 48 | labels = labels.cast_to(xm::Int32) 49 | TupleDataset.new(images, labels) 50 | else 51 | images 52 | end 53 | end 54 | end 55 | end 56 | end 57 | 58 | -------------------------------------------------------------------------------- /lib/chainer/datasets/mnist.rb: -------------------------------------------------------------------------------- 1 | require 'datasets' 2 | 3 | module Chainer 4 | module Datasets 5 | module MNIST 6 | def self.get_mnist(withlabel: true, ndim: 1, scale: 1.0, dtype: nil, label_dtype: nil) 7 | xm = Chainer::Device.default.xm 8 | dtype ||= xm::SFloat 9 | label_dtype ||= xm::Int32 10 | 11 | train_raw = retrieve_mnist(type: :train) 12 | train = preprocess_mnist(train_raw, withlabel, ndim, scale, dtype, label_dtype) 13 | 14 | test_raw = retrieve_mnist(type: :test) 15 | test = preprocess_mnist(test_raw, withlabel, ndim, scale, dtype, label_dtype) 16 | [train, test] 17 | end 18 | 19 | def self.preprocess_mnist(raw, withlabel, ndim, scale, image_dtype, label_dtype) 20 | images = raw[:x] 21 | if ndim == 2 22 | images = images.reshape(true, 28, 28) 23 | elsif ndim == 3 24 | images = images.reshape(true, 1, 28, 28) 25 | elsif ndim != 1 26 | raise "invalid ndim for MNIST dataset" 27 | end 28 | 29 | images = images.cast_to(image_dtype) 30 | images *= scale / 255.0 31 | 32 | if withlabel 33 | labels = raw[:y].cast_to(label_dtype) 34 | TupleDataset.new(images, labels) 35 | else 36 | images 37 | end 38 | end 39 | 40 | def self.retrieve_mnist(type:) 41 | train_table = ::Datasets::MNIST.new(type: type).to_table 42 | 43 | xm = Chainer::Device.default.xm 44 | { x: xm::UInt8[*train_table[:pixels]], y: xm::UInt8[*train_table[:label]] } 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/chainer/datasets/tuple_dataset.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Datasets 3 | class TupleDataset 4 | def initialize(*datasets) 5 | if datasets.empty? 6 | raise "no datasets are given" 7 | end 8 | length = datasets[0].shape[0] 9 | 10 | datasets.each_with_index do |dataset, idx| 11 | raise "dataset of the index #{idx} has a wrong length" unless dataset.shape[0] == length 12 | end 13 | 14 | @datasets = datasets 15 | @length = length 16 | end 17 | 18 | def [](index) 19 | batches = @datasets.map do |dataset| 20 | dataset.ndim > 1 ? dataset[index, false] : dataset[index] 21 | end 22 | if index.kind_of?(Enumerable) 23 | length = batches[0].shape[0] 24 | length.times.map {|i| batches.map { |m| m.ndim > 1 ? m[i, false] : m[i] } } 25 | else 26 | batches 27 | end 28 | end 29 | 30 | def size 31 | @length 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/chainer/device.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Device 3 | # Creates device 4 | # 5 | # @param [Integer or Chainer::AbstractDevice] device_spec Device specifier. 6 | # Negative integer indicates CPU. 0 or positive integer indicates GPU. 7 | # If a device object is given, itself is returned. 8 | # @return [Chainer::AbstractDevice] device object 9 | def create(device_spec) 10 | return device_spec if device_spec.kind_of?(AbstractDevice) 11 | if device_spec.kind_of?(Integer) 12 | return CpuDevice.new if device_spec < 0 13 | return GpuDevice.new(device_spec) 14 | end 15 | raise "Invalid device_spec: #{device_spec}" 16 | end 17 | module_function :create 18 | 19 | # Changes default device 20 | # 21 | # @param [Object] device_spec 22 | # @see Chainer::Device.create 23 | def change_default(device_spec) 24 | @default = create(device_spec) 25 | @default.use 26 | end 27 | module_function :change_default 28 | 29 | # Gets default device 30 | # 31 | # @return [Chainer::AbstractDevice] the default device. 32 | def default 33 | @default ||= CpuDevice.new 34 | end 35 | module_function :default 36 | 37 | # TODO(sonots): Add Device.from_array after Cumo provides an API 38 | # to return GPU device ID from Cumo::NArray. 39 | end 40 | 41 | class AbstractDevice 42 | def xm 43 | raise NotImplementedError 44 | end 45 | 46 | def use 47 | end 48 | end 49 | 50 | class CpuDevice < AbstractDevice 51 | def xm 52 | Numo 53 | end 54 | 55 | def ==(other) 56 | return false unless other.is_a?(CpuDevice) 57 | true 58 | end 59 | end 60 | 61 | class GpuDevice < AbstractDevice 62 | attr_reader :id 63 | 64 | # @param [Integer] id GPU Device ID. If not given, CUDA current device id is used. 65 | def initialize(id = nil) 66 | Chainer::CUDA.check_available 67 | id ||= Cumo::CUDA::Runtime.cudaGetDevice 68 | if id < 0 69 | raise 'GPU Device ID must not be negative' 70 | end 71 | @id = id 72 | end 73 | 74 | def xm 75 | Cumo 76 | end 77 | 78 | def ==(other) 79 | return false unless other.is_a?(GpuDevice) 80 | id == other.id 81 | end 82 | 83 | # Sets CUDA current device with owned GPU Device ID 84 | def use 85 | Cumo::CUDA::Runtime.cudaSetDevice(@id) 86 | end 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /lib/chainer/function.rb: -------------------------------------------------------------------------------- 1 | require 'chainer/function_node' 2 | module Chainer 3 | class Function 4 | 5 | attr_reader :rank, :inputs, :outputs, :retain_after_backward 6 | attr_accessor :output_data, :owned_node 7 | 8 | def initialize 9 | @rank = 0 10 | end 11 | 12 | def call(*inputs) 13 | node = self.node 14 | 15 | node.function = self 16 | node.weak_function = nil 17 | @node = WeakRef.new(node) 18 | @owned_node = nil 19 | 20 | ret = node.apply(inputs) 21 | 22 | ret.size == 1 ? ret[0] : ret 23 | end 24 | 25 | def inputs 26 | @node.inputs 27 | end 28 | 29 | def outputs 30 | @node.outputs 31 | end 32 | 33 | def node 34 | noderef = @node 35 | nd = noderef ? noderef.__getobj__ : @owned_node 36 | return nd if nd 37 | 38 | nd = FunctionAdapter.new(self) 39 | @owned_node = nd 40 | nd 41 | end 42 | 43 | def output_data 44 | node.output_data 45 | end 46 | 47 | def rank 48 | @node.rank 49 | end 50 | 51 | def label 52 | self.class.to_s 53 | end 54 | 55 | def forward(inputs) 56 | xm = Chainer.get_array_module(*inputs) 57 | if xm == Cumo 58 | forward_gpu(inputs) 59 | else 60 | forward_cpu(inputs) 61 | end 62 | end 63 | 64 | def forward_cpu(inputs) 65 | raise NotImplementedError 66 | end 67 | 68 | def forward_gpu(inputs) 69 | raise NotImplementedError 70 | end 71 | 72 | def backward(inputs, grad_outputs) 73 | xm = Chainer.get_array_module(*(inputs + grad_outputs)) 74 | if xm == Cumo 75 | backward_gpu(inputs, grad_outputs) 76 | else 77 | backward_cpu(inputs, grad_outputs) 78 | end 79 | end 80 | 81 | def backward_cpu(inputs, grad_outputs) 82 | return [nil] * inputs.size 83 | end 84 | 85 | def backward_gpu(inputs, grad_outputs) 86 | return [nil] * inputs.size 87 | end 88 | 89 | def retain_inputs(indexes) 90 | @input_indexes_to_retain = indexes 91 | end 92 | 93 | def retain_outputs(indexes, retain_after_backward: false) 94 | node.retain_outputs(indexes) 95 | end 96 | end 97 | 98 | class FunctionAdapter < ::Chainer::FunctionNode 99 | attr_accessor :function, :weak_function 100 | 101 | def initialize(function) 102 | super() 103 | @weak_function = WeakRef.new(function) 104 | function.owned_node = self 105 | end 106 | 107 | def function 108 | func = @function 109 | return func if func 110 | 111 | weak_func = @weak_function 112 | weak_func.__getobj__ 113 | end 114 | 115 | def label 116 | @function.label 117 | end 118 | 119 | def forward(inputs) 120 | retain_inputs(inputs.size.times.to_a) 121 | @function.forward(inputs) 122 | end 123 | 124 | def backward(target_input_indexes, grad_outputs) 125 | in_data = @inputs.map { |input| input.data } 126 | grad_out_data = grad_outputs.map { |grad| grad.nil? ? nil : grad.data } 127 | 128 | gxs = @function.backward(in_data, grad_out_data) 129 | ret = [] 130 | target_input_indexes.each do |i| 131 | if gxs[i].nil? 132 | g = nil 133 | else 134 | g = Chainer::Variable.new(gxs[i]) 135 | g.node.old_style_grad_generator = @function.label 136 | end 137 | ret << g 138 | end 139 | 140 | ret 141 | end 142 | end 143 | end 144 | -------------------------------------------------------------------------------- /lib/chainer/functions/activation/leaky_relu.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Functions 3 | module Activation 4 | # Leaky rectifier unit. 5 | class LeakyReLU < FunctionNode 6 | # Leaky Rectified Linear Unit function. 7 | # 8 | # This function is expressed as 9 | # 10 | # $$ 11 | # f(x)=\\max(x, ax), 12 | # $$ 13 | # 14 | # where $a$ is a configurable slope value. 15 | # 16 | # @param [Chainer::Variable or Numo::NArray or Cumo::NArray] x Input variable. A $(s_1, s_2, ..., s_N)$-shaped float array. 17 | # @param [float] slope Slope value $a$. 18 | # @return [Chainer::Variable] Output variable. A $(s_1, s_2, ..., s_N)$-shaped float array. 19 | # @example 20 | # > x = Numo::SFloat[[-1, 0], [2, -3], [-2, 1]] 21 | # > x 22 | # => Numo::SFloat#shape=[3,2] 23 | # [[-1, 0], 24 | # [2, -3], 25 | # [-2, 1]] 26 | # > F = Chainer::Functions::Activation::LeakyReLU 27 | # > F.leaky_relu(x, slope:0.2).data 28 | # => Numo::SFloat#shape=[3,2] 29 | # [[-0.2, 0], 30 | # [2, -0.6], 31 | # [-0.4, 1]] 32 | # 33 | def self.leaky_relu(x, slope: 0.2) 34 | self.new(slope: slope).apply([x])[0] 35 | end 36 | 37 | def initialize(slope:0.2) 38 | @slope = slope 39 | end 40 | 41 | def forward(inputs) 42 | x, = inputs 43 | y = x.dup 44 | y[x < 0] *= @slope 45 | if @slope >= 0 46 | retain_outputs([0]) 47 | else 48 | retain_inputs([0]) 49 | end 50 | [y] 51 | end 52 | 53 | def backward(indexes, grad_outputs) 54 | if @slope >= 0 55 | x = nil 56 | y = get_retained_outputs.first.data 57 | else 58 | x = get_retained_inputs.first.data 59 | y = nil 60 | end 61 | LeakyReLUGrad.new(x, y, @slope).apply(grad_outputs) 62 | end 63 | end 64 | 65 | class LeakyReLUGrad < FunctionNode 66 | def initialize(x, y, slope) 67 | @x = x 68 | @y = y 69 | @slope = slope 70 | end 71 | 72 | def forward(inputs) 73 | gy, = inputs 74 | gy = gy.dup 75 | if @slope >= 0 76 | gy[@y < 0] *= @slope 77 | else 78 | gy[@x < 0] *= @slope 79 | end 80 | [gy] 81 | end 82 | 83 | def backward(indexes, grad_outputs) 84 | LeakyReLUGrad.new(@x, @y, @slope).apply(grad_outputs) 85 | end 86 | end 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /lib/chainer/functions/activation/log_softmax.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Functions 3 | module Activation 4 | def self.logsumexp(x) 5 | xm = Chainer.get_array_module(x) 6 | m = x.max(axis: 1, keepdims: true) 7 | y = x - m 8 | y = xm::NMath.exp(y) 9 | s = y.sum(axis: 1, keepdims: true) 10 | s = xm::NMath.log(s) 11 | m + s 12 | end 13 | 14 | def self._log_softmax(x) 15 | log_z = logsumexp(x) 16 | x - log_z 17 | end 18 | 19 | # Log-softmax activation function. 20 | class LogSoftmax < FunctionNode 21 | # Channel-wise log-softmax function. 22 | # 23 | # This function computes its logarithm of softmax along the second axis. 24 | # Let $c = (c_1, c_2, \\dots, c_D)$ be the slice of +x+ along with 25 | # the second axis. For each slice $c$, it computes the logarithm of 26 | # the function $f(\c)$ defined as 27 | # 28 | # $$ 29 | # f(\c) = { \\exp(\c) \\over \\sum_{ d } \\exp(c_d) }. 30 | # $$ 31 | # 32 | # This method is theoretically equivalent to +log(softmax(x))+ but is more 33 | # stable. 34 | # 35 | # @note 36 | # +log(softmax(x))+ may cause underflow when +x+ is too small, 37 | # because +softmax(x)+ may returns +0+. 38 | # +log_softmax+ method is more stable. 39 | # 40 | # @param [Chainer::Variable or Numo::NArray or Cumo::NArray] x Input variable. A $n$-dimensional ($n \\geq 2$) float array. 41 | # @return [Chainer::Variable] Output variable. A $n$-dimensional ($n \\geq 2$) float array, which is the same shape with x. 42 | # 43 | # @see Chainer::Functions::Softmax 44 | # 45 | # @example 46 | # > x = Numo::SFloat[[0, 1, 2], [0, 2, 4]] 47 | # => Numo::SFloat#shape=[2,3] 48 | # [[0, 1, 2], 49 | # [0, 2, 4]] 50 | # > F = Chainer::Functions::Activation::LogSoftmax 51 | # > F.log_softmax(x).data 52 | # => Numo::SFloat#shape=[2,3] 53 | # [[-2.40761, -1.40761, -0.407606], 54 | # [-4.14293, -2.14293, -0.142932]] 55 | # @example (T.B.I : F.log, F.softmax) 56 | # > F.log_softmax(x).data.nearly_eq(F.log(F.softmax(x)).data).all?) 57 | # => true 58 | # 59 | def self.log_softmax(x) 60 | self.new.apply([x]).first 61 | end 62 | 63 | def forward(xs) 64 | y = Chainer::Functions::Activation._log_softmax(xs[0]) 65 | @x_shape = xs[0].shape 66 | @x_dtype = xs[0].class 67 | retain_outputs([0]) 68 | [y] 69 | end 70 | 71 | def backward(indexes, gy) 72 | y = get_retained_outputs.first 73 | LogSoftmaxGrad.new(@x_shape, @x_dtype).apply([y, gy[0]]) 74 | end 75 | end 76 | 77 | class LogSoftmaxGrad < FunctionNode 78 | def initialize(x_shape, x_dtype) 79 | @x_shape = x_shape 80 | @x_dtype = x_dtype 81 | end 82 | 83 | def forward(inputs) 84 | retain_inputs([0, 1]) 85 | y, gy = inputs 86 | 87 | xm = Chainer.get_array_module(y) 88 | gx = gy - xm::NMath.exp(y) * gy.sum(axis: 1, keepdims: true) 89 | [gx] 90 | end 91 | 92 | def backward(indexes, ggx) 93 | y, gy = get_retained_inputs 94 | ret = [] 95 | exp_y = Chainer::Functions::Math::Exp.exp(y) 96 | 97 | if indexes.include?(0) 98 | gy_sum = Chainer::Functions::Math::Sum.sum(gy, axis: 1, keepdims: true) 99 | gy_sum = Chainer::Functions::Array::BroadcastTo.broadcast_to(gy_sum, gy.shape) 100 | 101 | g0 = -ggx.first * exp_y * gy_sum 102 | ret << g0 103 | end 104 | if indexes.include?(1) 105 | a = Chainer::Functions::Math::Sum.sum(ggx.first * exp_y, axis: 1, keepdims: true) 106 | a = Chainer::Functions::Array::BroadcastTo.broadcast_to(a, gy.shape) 107 | g1 = ggx.first - a 108 | ret << g1 109 | end 110 | 111 | ret 112 | end 113 | end 114 | end 115 | end 116 | end 117 | -------------------------------------------------------------------------------- /lib/chainer/functions/activation/relu.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Functions 3 | module Activation 4 | # Rectified Linear Unit. 5 | class Relu < FunctionNode 6 | # Rectified Linear Unit function. 7 | # 8 | # $$ 9 | # f(x)=\\max(0, x). 10 | # $$ 11 | # 12 | # @param [Chainer::Variable or Numo::NArray or Cumo::NArray] x Input variable. A $(s_1, s_2, ..., s_N)$-shaped float array. 13 | # @return [Chainer::Variable] Output variable. A $(s_1, s_2, ..., s_N)$-shaped float array. 14 | # @example 15 | # > x = Numo::SFloat[[-1, 0], [2, -3], [-2, 1]] 16 | # > (x < 0).any? 17 | # => true 18 | # > F = Chainer::Functions::Activation::Relu 19 | # > y = F.relu(x) 20 | # > (y.data < 0).any? 21 | # => false 22 | # > y.shape 23 | # => [3, 2] 24 | # 25 | def self.relu(x) 26 | y, = self.new.apply([x]) 27 | y 28 | end 29 | 30 | def forward(x) 31 | retain_outputs([0]) 32 | [Utils::Array.force_array(x[0].class.maximum(x[0], 0))] 33 | end 34 | 35 | def backward(indexes, gy) 36 | y = get_retained_outputs.first 37 | ReLUGrad2.new(y).apply([gy[0]]) 38 | end 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/chainer/functions/activation/relu_grad2.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Functions 3 | module Activation 4 | # Computes the gradient of the ReLU function. 5 | # 6 | # This function takes 2 variables b and c, and 7 | # computes f(b, c) = sign(b) * c with backpropagation 8 | # where operations are dones in elementwise manner 9 | # and sign(x) = 1 when x > 0 is positive and 0 otherwise. 10 | # As the gradient of f with respect to b is 0, 11 | # we do not backpropagate errors toward b for computational efficiency. 12 | class ReLUGrad2 < FunctionNode 13 | def initialize(b) 14 | @b = b.data 15 | end 16 | 17 | def forward(inputs) 18 | y = inputs[0] * (@b > 0) 19 | [Utils::Array.force_array(y, y.class)] 20 | end 21 | 22 | def backward(indexes, gy) 23 | [gy[0] * heaviside(@b)] 24 | end 25 | 26 | private 27 | 28 | def heaviside(x) 29 | (x > 0).cast_to(x.class) 30 | end 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/chainer/functions/activation/sigmoid.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Functions 3 | module Activation 4 | # Logistic sigmoid function. 5 | class Sigmoid < FunctionNode 6 | # Element-wise sigmoid logistic function. 7 | # 8 | # $$ 9 | # f(x)=(1 + \\exp(-x))^ { -1 }. 10 | # $$ 11 | # 12 | # @param [Chainer::Variable or Numo::NArray or Cumo::NArray] x Input variable. A $(s_1, s_2, ..., s_N)$-shaped float array. 13 | # @return [Chainer::Variable] Output variable. A $(s_1, s_2, ..., s_N)$-shaped float array. 14 | # @example It maps the input values into the range of $`[0, 1]`$. 15 | # > x = Numo::SFloat.new(3).seq(-2, 2) 16 | # => Numo::SFloat#shape=[3] 17 | # [-2, 0, 2] 18 | # > F = Chainer::Functions::Activation::Sigmoid 19 | # > F.sigmoid(x).data 20 | # => Numo::SFloat#shape=[3] 21 | # [0.119203, 0.5, 0.880797] 22 | # 23 | def self.sigmoid(x) 24 | self.new.apply([x]).first 25 | end 26 | 27 | def forward(inputs) 28 | x, = inputs 29 | half = 0.5 30 | xm = Chainer.get_array_module(x) 31 | y = Utils::Array.force_array((xm::NMath.tanh(x * half) * half)+ half) 32 | retain_outputs([0]) 33 | [y] 34 | end 35 | 36 | def backward(indexes, grad_outputs) 37 | x = nil 38 | y = get_retained_outputs.first 39 | gy, = grad_outputs 40 | Chainer::Functions::Activation::SigmoidGrad.new([x]).apply([y, gy]) 41 | end 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/chainer/functions/activation/sigmoid_grad.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Functions 3 | module Activation 4 | # Logistic sigmoid gradient function. 5 | class SigmoidGrad < FunctionNode 6 | def initialize(inputs) 7 | @x, = inputs 8 | end 9 | 10 | def forward(inputs) 11 | retain_inputs([0, 1]) 12 | y, gy = inputs 13 | one = 1 14 | [Utils::Array.force_array(gy * y * (one - y))] 15 | end 16 | 17 | def backward(indexes, grad_outputs) 18 | y, gy = get_retained_inputs 19 | g, = grad_outputs 20 | [g * gy * ( 1 -2 * y), g * y * (1 - y)] 21 | end 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/chainer/functions/activation/tanh.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Functions 3 | module Activation 4 | # Hyperbolic tangent function. 5 | class Tanh < FunctionNode 6 | # Elementwise hyperbolic tangent function. 7 | # 8 | # $$ 9 | # f(x)=\\tanh(x). 10 | # $$ 11 | # 12 | # @param [Chainer::Variable or Numo::NArray or Cumo::NArray] x Input variable. A $(s_1, s_2, ..., s_N)$-shaped float array. 13 | # @return [Chainer::Variable] Output variable. A $(s_1, s_2, ..., s_N)$-shaped float array. 14 | # @example 15 | # > x = Numo::SFloat.new(3).seq(-1, 2) 16 | # => Numo::SFloat#shape=[3] 17 | # [-1, 1, 3] 18 | # > F = Chainer::Functions::Activation::Tanh 19 | # > F.tanh(x).data 20 | # => Numo::SFloat#shape=[3] 21 | # [-0.761594, 0.761594, 0.995055] 22 | # 23 | def self.tanh(x) 24 | self.new.apply([x]).first 25 | end 26 | 27 | def forward(x) 28 | xm = Chainer.get_array_module(x[0]) 29 | y = Utils::Array.force_array(xm::NMath.tanh(x[0])) 30 | retain_outputs([0]) 31 | @use_cudnn = false 32 | [y] 33 | end 34 | 35 | def backward(indexes, grad_outputs) 36 | if @use_cudnn 37 | x = get_retained_inputs.first.data 38 | else 39 | x = nil 40 | end 41 | 42 | y = get_retained_outputs.first 43 | gy = grad_outputs.first 44 | TanhGrad.new(x).apply([y, gy]) 45 | end 46 | end 47 | 48 | class TanhGrad < FunctionNode 49 | def initialize(x) 50 | super() 51 | 52 | # The original input `x` is only required for cuDNN. 53 | # If it is None, this class does not use cuDNN. 54 | # Note that x must be c-contiguous and it is checked 55 | # in Tanh.forward_gpu. 56 | @x = x 57 | end 58 | 59 | def forward(inputs) 60 | retain_inputs([0, 1]) 61 | y, gy = inputs 62 | 63 | one = y.class.new.fill(1) 64 | [Utils::Array.force_array(gy * (one - y * y))] 65 | end 66 | 67 | def backward(indexes, grad_outputs) 68 | y, gy = get_retained_inputs 69 | g = grad_outputs[0] 70 | 71 | y_mul_g = y * g 72 | grad_y = -2 * gy * y_mul_g 73 | ggy = g - y * y_mul_g 74 | [grad_y, ggy] 75 | end 76 | end 77 | end 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /lib/chainer/functions/array/broadcast_to.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Functions 3 | module Array 4 | # Function that broadcasts an array to a new shape. 5 | class BroadcastTo < FunctionNode 6 | def initialize(shape) 7 | @shape = shape 8 | end 9 | 10 | def self.broadcast_to(x, shape) 11 | return Chainer::Variable.as_variable(x) if x.shape == shape 12 | self.new(shape).apply([x]).first 13 | end 14 | 15 | def forward(inputs) 16 | x = inputs.first 17 | [Chainer::Utils::Array.broadcast_to(x, @shape)] 18 | end 19 | 20 | def backward(indexes, grad_outputs) 21 | gx = grad_outputs.first 22 | shape = @inputs.first.shape 23 | ndim = shape.size 24 | lead = gx.ndim - ndim 25 | lead_axis = lead.times.to_a 26 | axis = shape.each_with_object([]).with_index do |(sx, res), i| 27 | next unless sx == 1 28 | res << i + lead 29 | end 30 | gx = Chainer::Functions::Math::Sum.sum(gx, axis: lead_axis + axis, keepdims: true) 31 | return [Chainer::Functions::Array::Squeeze.squeeze(gx, axis: lead_axis)] if lead > 0 32 | [gx] 33 | end 34 | 35 | private 36 | 37 | def backward_one(shape, dtype, g) 38 | return dtype.zeros(shape) unless g 39 | 40 | ndim = shape.size 41 | if g.ndim != ndim 42 | g = g.sum(axis: 0...(g.ndim - ndim)) 43 | end 44 | 45 | axis = shape.each_with_index.select{|sx, i| sx == 1 }.map{|sx, i| i } 46 | if axis.size > 0 47 | g.sum(keepdims: true, axis: axis) 48 | else 49 | g 50 | end 51 | end 52 | end 53 | end 54 | end 55 | end 56 | 57 | -------------------------------------------------------------------------------- /lib/chainer/functions/array/cast.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Functions 3 | module Array 4 | class Cast < FunctionNode 5 | # Cast an input variable to a given type. 6 | # 7 | # @param x [Chainer::Variable or Numo::Narray] x : Input variable to be casted. 8 | # @param type [Numo::Narray class] type : data class to cast 9 | # @return [Chainer::Variable] Variable holding a casted array. 10 | # 11 | # example 12 | # > x = Numo::UInt8.new(3, 5).seq 13 | # > x.class 14 | # # => Numo::UInt8 15 | # > y = Chainer::Functions::Array::Cast.cast(x, Numo::DFloat) 16 | # > y.dtype 17 | # # => Numo::DFloat 18 | def self.cast(x, type) 19 | if (Chainer.array?(x) && x.class == type) || (x.is_a?(Chainer::Variable) && x.dtype == type) 20 | return Chainer::Variable.as_variable(x) 21 | end 22 | self.new(type).apply([x]).first 23 | end 24 | 25 | def initialize(type) 26 | @type = type 27 | end 28 | 29 | def forward(x) 30 | @in_type = x.first.class 31 | [x.first.cast_to(@type)] 32 | end 33 | 34 | def backward(indexes, g) 35 | [Cast.cast(g.first, @in_type)] 36 | end 37 | end 38 | end 39 | end 40 | end 41 | 42 | -------------------------------------------------------------------------------- /lib/chainer/functions/array/reshape.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Functions 3 | module Array 4 | # Reshapes an input array without copy. 5 | class Reshape < FunctionNode 6 | def initialize(shape) 7 | @shape = shape 8 | end 9 | 10 | def self.reshape(x, shape) 11 | return Chainer::Variable.as_variable(x) if x.shape == shape 12 | return self.new(shape).apply([x]).first 13 | end 14 | 15 | def forward(inputs) 16 | x = inputs.first 17 | new_shape = @shape.map { |s| s == -1 ? nil : s } 18 | [x.reshape(*new_shape)] 19 | end 20 | 21 | def backward(indexes, grad_outputs) 22 | gx = grad_outputs.first 23 | [Reshape.reshape(gx, @inputs.first.shape)] 24 | end 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/chainer/functions/array/rollaxis.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Functions 3 | module Array 4 | # Roll axis of an array. 5 | class Rollaxis < FunctionNode 6 | # Roll the axis backwards to the given position. 7 | # 8 | # @param [Chainer::Variable] x Input variable 9 | # @param [Integer] axis The axis to roll backwards. 10 | # @param [Integer] start The place to which the axis is moved. 11 | # @return [Chainer::Variable] Variable whose axis is rolled. 12 | def self.rollaxis(x, axis, start: 0) 13 | Rollaxis.new(axis, start).apply([x]).first 14 | end 15 | 16 | def initialize(axis, start) 17 | unless axis.is_a?(Integer) 18 | raise ArgumentError, 'axis must be int' 19 | end 20 | 21 | unless start.is_a?(Integer) 22 | raise ArgumentError, 'start must be int' 23 | end 24 | 25 | @axis = axis 26 | @start = start 27 | end 28 | 29 | def forward(inputs) 30 | retain_inputs([]) 31 | @in_ndim = inputs.first.ndim 32 | 33 | [Chainer::Utils::Array.rollaxis(inputs.first, @axis, start: @start)] 34 | end 35 | 36 | def backward(indexes, gy) 37 | axis = @axis 38 | if axis < 0 39 | axis += @in_ndim 40 | end 41 | start = @start 42 | if start < 0 43 | start += @in_ndim 44 | end 45 | 46 | if axis > start 47 | axis += 1 48 | else 49 | start -= 1 50 | end 51 | 52 | Rollaxis.new(start, axis).apply(gy) 53 | end 54 | end 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /lib/chainer/functions/array/select_item.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Functions 3 | module Array 4 | # Select elements stored in given indices. 5 | class SelectItem < FunctionNode 6 | # Select elements stored in given indices. 7 | # This function returns $t.choose(x.T)$, that means 8 | # $y[i] == x[i, t[i]]$ for all $i$. 9 | # 10 | # @param [Chainer::Variable] x Variable storing arrays. 11 | # @param [Chainer::Variable] t Variable storing index numbers. 12 | # @return [Chainer::Variable] Variable that holds $t$-th element of $x$. 13 | def self.select_item(x, t) 14 | SelectItem.new.apply([x, t]).first 15 | end 16 | 17 | def forward(inputs) 18 | retain_inputs([1]) 19 | x, t = inputs 20 | @in_shape = x.shape 21 | @in_dtype = x.class 22 | 23 | return [x.class.zeros(0)] if t.size == 0 24 | 25 | # x[six.moves.range(t.size), t] 26 | [x[true, t].diagonal.dup] 27 | end 28 | 29 | def backward(indexes, gy) 30 | t = get_retained_inputs.first 31 | ret = [] 32 | if indexes.include?(0) 33 | ggx = Assign.new(@in_shape, @in_dtype, t).apply(gy).first 34 | ret << ggx 35 | end 36 | if indexes.include?(1) 37 | ret << nil 38 | end 39 | ret 40 | end 41 | end 42 | 43 | class Assign < FunctionNode 44 | def initialize(shape, dtype, t) 45 | @shape = shape 46 | @dtype = dtype 47 | @t = t.data 48 | end 49 | 50 | def forward(inputs) 51 | gx = @dtype.zeros(*@shape) 52 | 53 | # gx[six.moves.range(self.t.size), self.t] = inputs[0] 54 | gx.at((0...@t.size).to_a, @t)[true] = inputs[0] unless @t.empty? 55 | 56 | [gx] 57 | end 58 | 59 | def backward(indexes, gy) 60 | SelectItem.new.apply([gy[0], @t]) 61 | end 62 | end 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /lib/chainer/functions/array/squeeze.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Functions 3 | module Array 4 | class Squeeze < FunctionNode 5 | # Remove demensions of size one from the shape of a Numo::NArray. 6 | # @param [Chainer::Variable or Numo::NArray] x Input data. 7 | # @param [nil or integer or array of integer] axis A subset of the single-dimensional entries in the shape to remove. 8 | # If `nil` is supplied, all of them are removed. The dimension index starts at zero. 9 | # If an axis with dimension greater than one is selected, an error is raised. 10 | # @return [Chainer::Variable] Variable whose dimensions of size 1 are removed. 11 | def self.squeeze(x, axis: nil) 12 | self.new(axis: axis).apply([x]).first 13 | end 14 | 15 | def initialize(axis: nil) 16 | if axis.nil? 17 | @axis = nil 18 | elsif axis.kind_of?(Integer) 19 | @axis = [axis] 20 | elsif axis.kind_of?(::Array) && Array(axis).all? { |i| i.kind_of?(Integer) } 21 | @axis = axis 22 | else 23 | raise TypeError, 'axis must be None, int or tuple of ints' 24 | end 25 | end 26 | 27 | def forward(inputs) 28 | x = inputs.first 29 | shape = x.shape 30 | 31 | # TODO: numpy.squeeze 32 | if @axis.nil? 33 | new_shape = shape.reject { |axis| axis == 1 } 34 | else 35 | new_shape = shape 36 | @axis.map do |a| 37 | raise StandardError, "cannot select an axis to squeeze out which has size not equal to one" unless shape[a] == 1 38 | new_shape[a] = nil 39 | end 40 | new_shape.compact! 41 | end 42 | ret = new_shape.size.zero? ? x.class.new.fill(x[0]) : x.reshape(*new_shape) 43 | 44 | [ret] 45 | end 46 | 47 | def backward(indexes, grad_outputs) 48 | if @axis.nil? 49 | axis = argone(@inputs[0].shape) 50 | else 51 | axis = @axis 52 | ndim = @inputs[0].shape.size 53 | axis = axis.map { |x| x < 0 ? x + ndim : x } 54 | axis.sort! 55 | end 56 | gx = grad_outputs.first 57 | 58 | shape = gx.shape 59 | axis.each do |x| 60 | shape.insert(x, 1) 61 | end 62 | [gx.reshape(*shape)] 63 | end 64 | 65 | private 66 | 67 | def argone(iterable) 68 | result = [] 69 | Array(iterable).each_with_index do |x, i| 70 | raise StandardError, "elements in iterable must be int" unless x.kind_of?(Integer) 71 | result << i if x == 1 72 | end 73 | result 74 | end 75 | end 76 | end 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /lib/chainer/functions/array/transpose.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Functions 3 | module Array 4 | # Permute the dimensions of an array. 5 | class Transpose < FunctionNode 6 | # Permute the dimensions of an input variable without copy. 7 | # 8 | # @param [Chainer::Variable] x Input Variable. 9 | # @param [::Array] axes By default, reverse the dimensions, 10 | # otherwise permute the axes according to the values given. 11 | # @return [Chainer::Variable] Variable whose axes are permuted. 12 | def self.transpose(x, axes: nil) 13 | Transpose.new(axes: axes).apply([x]).first 14 | end 15 | 16 | def initialize(axes: nil) 17 | @axes = axes 18 | end 19 | 20 | def label 21 | 'Transpose' 22 | end 23 | 24 | def forward(inputs) 25 | x = inputs.first 26 | [x.transpose(*@axes)] 27 | end 28 | 29 | def backward(indexes, grad_outputs) 30 | inv_axes = @axes 31 | if inv_axes 32 | axes_len = inv_axes.size 33 | 34 | axes = inv_axes.map { |ax| ax % axes_len } 35 | inv_axes = Numo::NArray[*axes].sort_index.to_a 36 | end 37 | 38 | Transpose.new(axes: inv_axes).apply(grad_outputs) 39 | end 40 | end 41 | end 42 | end 43 | end 44 | 45 | -------------------------------------------------------------------------------- /lib/chainer/functions/connection/convolution_2d_grad_w.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Functions 3 | module Connection 4 | class Convolution2DGradW < Chainer::FunctionNode 5 | def initialize(conv2d) 6 | w_node = conv2d.inputs[1] 7 | 8 | @kh, @kw = w_node.shape[2..-1] 9 | @sy = conv2d.sy 10 | @sx = conv2d.sx 11 | @ph = conv2d.ph 12 | @pw = conv2d.pw 13 | @cover_all = conv2d.cover_all 14 | @w_dtype = w_node.dtype 15 | @w_shape = w_node.shape 16 | end 17 | 18 | def forward(inputs) 19 | retain_inputs([0, 1]) 20 | x, gy = inputs 21 | 22 | xm = Chainer.get_array_module(x, gy) 23 | if xm == Cumo and Chainer::CUDA.cudnn_enabled? and !@cover_all 24 | return _forward_cudnn(x, gy) 25 | end 26 | 27 | col = Chainer::Utils::Conv.im2col(x, @kh, @kw, @sy, @sx, @ph, @pw, cover_all: @cover_all) 28 | gw = Chainer::Utils::Math.tensordot(gy, col, [[0, 2, 3], [0, 4, 5]]).cast_to(@w_dtype) 29 | [gw] 30 | end 31 | 32 | private def _forward_cudnn(x, gy) 33 | gy = gy.cast_to(x.class) 34 | gw = x.conv_grad_w(gy, @w_shape, stride: [@sy, @sx], pad: [@ph, @pw]) 35 | gw = gw.cast_to(@w_dtype) 36 | [gw] 37 | end 38 | 39 | def backward(indexes, grad_outputs) 40 | x, gy = get_retained_inputs 41 | ggw = grad_outputs.first 42 | 43 | ret = [] 44 | if indexes.include?(0) 45 | xh, xw = x.shape[2..-1] 46 | gx = Deconvolution2DFunction.deconvolution_2d(gy, ggw, stride: [@sy, @sx], pad: [@ph, @pw], outsize: [xh, xw]) 47 | ret << gx 48 | end 49 | 50 | if indexes.include?(1) 51 | ggy = Chainer::Functions::Connection::Convolution2DFunction.convolution_2d(x, ggw, stride: [@sy, @sx], pad: [@ph, @pw], cover_all: @cover_all) 52 | ret << ggy 53 | end 54 | 55 | ret 56 | end 57 | end 58 | end 59 | end 60 | end 61 | 62 | -------------------------------------------------------------------------------- /lib/chainer/functions/connection/embed_id.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Functions 3 | module Connection 4 | class EmbedIDFunction < Chainer::Function 5 | def initialize(ignore_label: nil) 6 | @ignore_label = ignore_label 7 | end 8 | 9 | def self.embed_id(x, w, ignore_label: nil) 10 | self.new(ignore_label: ignore_label).(x, w) 11 | end 12 | 13 | def forward(inputs) 14 | xm = Chainer.get_array_module(*inputs) 15 | (x, w) = inputs 16 | 17 | unless @ignore_label 18 | return [Chainer::Utils::Array.take(w, x, axis: 0)] 19 | end 20 | 21 | valid_x = x.ne(@ignore_label) 22 | if valid_x.count == x.size 23 | return [Chainer::Utils::Array.take(w, x, axis: 0)] 24 | end 25 | x *= valid_x 26 | y = Chainer::Utils::Array.take(w, x, axis: 0).dup 27 | 28 | y = y.reshape(y.shape.take(y.shape.size - 1).reduce(&:*), true) 29 | valid_x.where2.last.each {|i| y[i, true] = y.class.zeros(y.shape.last) } 30 | 31 | [y.reshape(*x.shape, true)] 32 | end 33 | 34 | def backward(inputs, grad_outputs) 35 | (x, w) = inputs 36 | gy = grad_outputs[0].reshape(x.size, true) 37 | gw = w.class.zeros(w.shape).reshape(w.shape.take(w.shape.size - 1).reduce(&:*), true) 38 | 39 | x.reshape(x.size).each_with_index do |ix, i| 40 | next if ix == @ignore_label 41 | gw[ix, true] = gw[ix, true] + gy[i, true] 42 | end 43 | 44 | [nil, gw.reshape(*w.shape)] 45 | end 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/chainer/functions/connection/linear.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Functions 3 | module Connection 4 | class LinearFunction < Chainer::FunctionNode 5 | def self.linear(x, w, b=nil) 6 | if x.ndim > 2 7 | x = x.reshape(x.shape.first, -1) 8 | end 9 | 10 | if b.nil? 11 | args = x, w 12 | else 13 | args = x, w, b 14 | end 15 | 16 | self.new.apply(args).first 17 | end 18 | 19 | def forward(inputs) 20 | x = inputs[0] 21 | w = inputs[1] 22 | 23 | y = x.dot(w.transpose).cast_to(x.class) 24 | if inputs.size == 3 25 | b = inputs[2] 26 | y += b 27 | end 28 | 29 | retain_inputs([0, 1]) 30 | return [y] 31 | end 32 | 33 | def backward(indexes, grad_outputs) 34 | x, w = get_retained_inputs 35 | gy = grad_outputs.first 36 | 37 | ret = [] 38 | if indexes.include?(0) 39 | gx = LinearFunction.linear(gy, w.transpose) 40 | ret << Chainer::Functions::Array::Cast.cast(gx, x.dtype) 41 | end 42 | if indexes.include?(1) 43 | gw = LinearFunction.linear(gy.transpose, x.transpose) 44 | ret << Chainer::Functions::Array::Cast.cast(gw, w.dtype) 45 | end 46 | if indexes.include?(2) 47 | gb = Chainer::Functions::Math::Sum.sum(gy, axis: 0) 48 | ret << gb 49 | end 50 | ret 51 | end 52 | end 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /lib/chainer/functions/evaluation/accuracy.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Functions 3 | module Evaluation 4 | class Accuracy < Function 5 | def self.accuracy(y, t, ignore_label: nil) 6 | self.new(ignore_label: ignore_label).(y, t) 7 | end 8 | 9 | def initialize(ignore_label: nil) 10 | @ignore_label = ignore_label 11 | end 12 | 13 | def forward(inputs) 14 | y, t = inputs 15 | xm = Chainer.get_array_module(*inputs) 16 | if @ignore_label 17 | mask = t.eq(@ignore_label) 18 | ignore_cnt = mask.count 19 | 20 | pred = y.max_index(axis: 1) - xm::Int32.new(y.shape[0]).seq(0, y.shape[1]) 21 | pred = pred.reshape(*t.shape) 22 | pred[mask] = @ignore_label 23 | count = pred.eq(t).count - ignore_cnt 24 | 25 | total = t.size - ignore_cnt 26 | 27 | if total == 0 28 | [y.class.cast(0.0)] 29 | else 30 | [y.class.cast(count.to_f / total)] 31 | end 32 | else 33 | pred = y.max_index(axis: 1) - xm::Int32.new(y.shape[0]).seq(0, y.shape[1]) 34 | pred = pred.reshape(*t.shape) 35 | 36 | [y.class.cast(y.class[pred.eq(t)].mean)] 37 | end 38 | end 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/chainer/functions/loss/mean_squared_error.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Functions 3 | module Loss 4 | # Mean squared error (a.k.a. Euclidean loss) function. 5 | class MeanSquaredError < FunctionNode 6 | # Mean squared error function. 7 | # 8 | # This function computes mean squared error between two variables. The mean 9 | # is taken over the minibatch. Note that the error is not scaled by 1/2. 10 | # 11 | # @param [Chainer::Variable or Numo::NArray or Cumo::NArray] x0 Input variable. 12 | # @param [Chainer::Variable or Numo::NArray or Cumo::NArray] x1 Input variable. 13 | # @return [Chainer::Variable] A variable holding an array representing the mean squared error of two inputs. 14 | # 15 | def self.mean_squared_error(x0, x1) 16 | self.new.apply([x0, x1]).first 17 | end 18 | 19 | def forward(inputs) 20 | retain_inputs([0, 1]) 21 | diff = (inputs[0] - inputs[1]).flatten.dup 22 | [diff.class.cast(diff.dot(diff) / diff.size)] 23 | end 24 | 25 | def backward(indexes, gy) 26 | x0, x1 = get_retained_inputs 27 | diff = x0 - x1 28 | gy0 = Chainer::Functions::Array::BroadcastTo.broadcast_to(gy[0], diff.shape) 29 | gx0 = gy0 * diff * (2.0 / diff.size) 30 | 31 | ret = [] 32 | if indexes.include?(0) 33 | ret << gx0 34 | end 35 | if indexes.include?(1) 36 | ret << -gx0 37 | end 38 | ret 39 | end 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/chainer/functions/math/basic_math.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Functions 3 | module Math 4 | class Neg < ::Chainer::FunctionNode 5 | def label 6 | '__neg__' 7 | end 8 | 9 | def forward(x) 10 | [Utils::Array.force_array(-x[0])] 11 | end 12 | 13 | def backward(indexes, gy) 14 | [-gy[0]] 15 | end 16 | end 17 | 18 | class Add < ::Chainer::FunctionNode 19 | def forward(x) 20 | [Utils::Array.force_array(x[0] + x[1])] 21 | end 22 | 23 | def backward(indexes, gy) 24 | [gy[0], gy[0]] 25 | end 26 | end 27 | 28 | class AddConstant < ::Chainer::FunctionNode 29 | def initialize(value) 30 | @value = value 31 | end 32 | 33 | def forward(x) 34 | [Utils::Array.force_array(x[0] + @value)] 35 | end 36 | 37 | def backward(indexes, gy) 38 | [gy[0]] 39 | end 40 | end 41 | 42 | class Sub < ::Chainer::FunctionNode 43 | def label 44 | '_ - _' 45 | end 46 | 47 | def forward(x) 48 | [Utils::Array.force_array(x[0] - x[1])] 49 | end 50 | 51 | def backward(indexes, gy) 52 | [gy[0], -gy[0]] 53 | end 54 | end 55 | 56 | class Mul < ::Chainer::FunctionNode 57 | def forward(x) 58 | retain_inputs([0, 1]) 59 | [Utils::Array.force_array(x[0] * x[1])] 60 | end 61 | 62 | def backward(indexes, gy) 63 | xs = get_retained_inputs 64 | indexes.map { |i| gy[0] * xs[1 - i] } 65 | end 66 | end 67 | 68 | class MulConstant < ::Chainer::FunctionNode 69 | def initialize(value) 70 | @value = value 71 | end 72 | 73 | def forward(x) 74 | [Utils::Array.force_array(@value * x[0])] 75 | end 76 | 77 | def backward(indexes, gy) 78 | [gy[0] * @value] 79 | end 80 | end 81 | 82 | class Div < ::Chainer::FunctionNode 83 | def forward(x) 84 | [Utils::Array.force_array(x[0] / x[1])] 85 | end 86 | 87 | def backward(indexes, gy) 88 | gx0 = Utils::Array.force_array(gy[0] / x[1]) 89 | [gx0, Utils::Array.force_array(-1 * gx0 * x[0] / x[1])] 90 | end 91 | end 92 | 93 | class PowVarVar < ::Chainer::FunctionNode 94 | def forward(x) 95 | @y = Utils::Array.force_array(x[0] ** x[1]) 96 | [@y] 97 | end 98 | 99 | def backward(x, gy) 100 | one = x[1].class.ones[0] 101 | gx0 = Utils::Array.force_array(x[1] * (x[0] ** (x[1] - one)) * gy[0]) 102 | xm = Chainer.get_array_module(x[0]) 103 | gx1 = Utils::Array.force_array(xm::NMath.log(x[0]) * @y * gy[0]) 104 | [gx0, gx1] 105 | end 106 | end 107 | 108 | class PowVarConst < ::Chainer::FunctionNode 109 | def initialize(value) 110 | @value = value 111 | end 112 | 113 | def forward(x) 114 | [Utils::Array.force_array(x[0] ** @value)] 115 | end 116 | 117 | def backward(x, gy) 118 | val_1 = @value - 1 119 | gx = @value * (x[0] ** val_1) * gy[0] 120 | [Utils::Array.force_array(gx)] 121 | end 122 | end 123 | end 124 | end 125 | end 126 | -------------------------------------------------------------------------------- /lib/chainer/functions/math/exp.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Functions 3 | module Math 4 | class Exp < Chainer::FunctionNode 5 | # Elementwise exponential function. 6 | def self.exp(x) 7 | self.new.apply([x]).first 8 | end 9 | 10 | def label 11 | 'exp' 12 | end 13 | 14 | def forward(x) 15 | retain_inputs([]) 16 | retain_outputs([0]) 17 | xm = Chainer.get_array_module(x.first) 18 | [Utils::Array.force_array(xm::NMath.exp(x.first))] 19 | end 20 | 21 | def backward(indexes, gy) 22 | y = get_retained_outputs.first 23 | [y * gy.first] 24 | end 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/chainer/functions/math/identity.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Functions 3 | module Math 4 | # Identity function. 5 | class Identity < Chainer::FunctionNode 6 | def check_type_forward(in_types) 7 | # pass 8 | end 9 | 10 | def forward(xs) 11 | retain_inputs([]) 12 | return xs 13 | end 14 | 15 | def backward(indexes, gys) 16 | return gys 17 | end 18 | 19 | # Just returns input variables. 20 | def self.identity(*inputs) 21 | ret = self.new.apply(inputs) 22 | ret.size == 1 ? ret[0] : ret 23 | end 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/chainer/functions/math/sum.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Functions 3 | module Math 4 | # Sum of array elements over a given axis. 5 | class Sum < Chainer::FunctionNode 6 | # Sum of array elements over a given axis 7 | # 8 | # @param [Chainer::Variable] x Elements to sum 9 | # @param [nil, Integer, Array] axis Axis which a sum is performed 10 | # @param[boolean] keepdims If `true`, the specified axes are remained as axes of length one 11 | # @return [Chainer::Variable] Output variable 12 | def self.sum(x, axis: nil, keepdims: false) 13 | Sum.new(axis: axis, keepdims: keepdims).apply([x]).first 14 | end 15 | 16 | def initialize(axis: nil, keepdims: false) 17 | if axis.nil? 18 | @axis = nil 19 | elsif axis.is_a?(Integer) 20 | @axis = [axis] 21 | elsif axis.is_a?(::Array) && axis.all? { |e| e.is_a?(Integer) } 22 | raise ArgumentError, "duplicate value in axis: #{axis}" unless axis.uniq.size == axis.size 23 | @axis = axis 24 | else 25 | raise TypeError, 'nil, Integer or Array of int are required' 26 | end 27 | 28 | @keepdims = keepdims 29 | end 30 | 31 | def forward(inputs) 32 | x = inputs.first 33 | ret = x.sum(axis: @axis, keepdims: @keepdims) 34 | ret = x.class.cast(ret) 35 | [ret] 36 | end 37 | 38 | def backward(indexes, grad_outputs) 39 | gy = grad_outputs.first 40 | ndim = @inputs.first.shape.size 41 | unless ndim == 0 || @axis.nil? || @keepdims 42 | actual_axis = @axis.map { |axis| axis >= 0 ? axis : axis + ndim } 43 | shape = gy.shape 44 | actual_axis.sort.each { |axis| shape.insert(axis, 1) } 45 | gy = Chainer::Functions::Array::Reshape.reshape(gy, shape) 46 | end 47 | [Chainer::Functions::Array::BroadcastTo.broadcast_to(gy, @inputs.first.shape)] 48 | end 49 | end 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/chainer/functions/noise/dropout.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Functions 3 | module Noise 4 | class Dropout < Chainer::FunctionNode 5 | attr_reader :mask 6 | # Drops elements of input variable randomly. 7 | # 8 | # This function drops input elements randomly with probability `ratio` and 9 | # scales the remaining elements by factor `1 / (1 - ratio)`. 10 | # In testing mode, it does nothing and just returns `x`. 11 | # 12 | # @param [Chainer::Variable] x Input variable. 13 | # @param [float] ratio Dropout ratio. The ``ratio`` must be `0.0 <= ratio < 1.0`. 14 | # @return [Chainer::Variable] Output variable. 15 | def self.dropout(x, ratio: 0.5) 16 | Chainer.configuration.train ? self.new(ratio).apply([x])[0] : Chainer::Variable.as_variable(x) 17 | end 18 | 19 | def initialize(dropout_ratio) 20 | if dropout_ratio < 0 || dropout_ratio >= 1.0 21 | raise 'dropout_ratio must be in the range [0, 1)' 22 | end 23 | @dropout_ratio = dropout_ratio 24 | end 25 | 26 | def forward(x) 27 | unless self.instance_variable_defined?(:@mask) 28 | scale = x[0].class[*[1.0 / (1 - @dropout_ratio)]][0] 29 | flag = x[0].class.new(*x[0].shape).rand >= @dropout_ratio 30 | 31 | @mask = x[0].class.zeros(*x[0].shape) 32 | @mask[flag] = 1 33 | @mask *= scale 34 | end 35 | [x[0] * @mask] 36 | end 37 | 38 | def backward(x, gy) 39 | DropoutGrad.new(@mask).apply(gy) 40 | end 41 | end 42 | 43 | # Computes the gradient of the Dropout function. 44 | class DropoutGrad < Chainer::FunctionNode 45 | def initialize(mask) 46 | @mask = mask 47 | end 48 | 49 | def forward(inputs) 50 | y = inputs.first * @mask 51 | [y] 52 | end 53 | 54 | def backward(indexes, gy) 55 | DropoutGrad.new(@mask).apply(gy) 56 | end 57 | end 58 | end 59 | end 60 | end 61 | 62 | -------------------------------------------------------------------------------- /lib/chainer/functions/pooling/average_pooling_2d.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Functions 3 | module Pooling 4 | class AveragePooling2D < Pooling2D 5 | attr_reader :in_shape, :in_dtype 6 | 7 | # Spatial average pooling function. 8 | # 9 | # This function acts similarly to :class:`Convolution2D`, 10 | # but it computes the average of input spatial patch for each channel 11 | # without any parameter instead of computing the inner products. 12 | # @param [Chainer::Variable] x Input variable. 13 | # @param [integer] ksize Size of pooling window. `ksize=k` and `ksize=[k, k]` are equivalent. 14 | # @param [integer] stride Stride of pooling applications. `stride=s` and `stride=[s, s]` are equivalent. 15 | # If `nil` is specified, then it uses same stride as the pooling window size. 16 | # @param [integer] pad Spatial padding width for the input array. `pad=p` and `pad=[p, p]` are equivalent. 17 | # @return [Chainer::Variable] Output variable 18 | def self.average_pooling_2d(x, ksize, stride: nil, pad: 0) 19 | self.new(ksize, stride: stride, pad: pad, cover_all: false).apply([x])[0] 20 | end 21 | 22 | # Average pooling over a set of 2d planes. 23 | def forward(x) 24 | @in_shape = x[0].shape 25 | @in_dtype = x[0].class 26 | 27 | xm = Chainer.get_array_module(x[0]) 28 | if @use_cudnn = (xm == Cumo and Chainer::CUDA.cudnn_enabled? and !@cover_all) 29 | return _forward_cudnn(x[0]) 30 | end 31 | 32 | col = Chainer::Utils::Conv.im2col(x[0], @kh, @kw, @sy, @sx, @ph, @pw) 33 | y = col.mean(axis: [2, 3]) 34 | 35 | [y] 36 | end 37 | 38 | private def _forward_cudnn(x) 39 | retain_inputs([0]) 40 | y = x.avg_pool([@kh, @kw], stride: [@sy, @sx], pad: [@ph, @pw], pad_value: 0) 41 | retain_outputs([0]) 42 | [y] 43 | end 44 | 45 | def backward(indexes, gy) 46 | AveragePooling2DGrad.new(self).apply(gy) 47 | end 48 | end 49 | 50 | class AveragePooling2DGrad < FunctionNode 51 | def initialize(apool2d) 52 | @kh = apool2d.kh 53 | @kw = apool2d.kw 54 | @sy = apool2d.sy 55 | @sx = apool2d.sx 56 | @ph = apool2d.ph 57 | @pw = apool2d.pw 58 | @use_cudnn = apool2d.use_cudnn 59 | @in_shape = apool2d.in_shape 60 | @in_dtype = apool2d.in_dtype 61 | @apool2d = apool2d 62 | end 63 | 64 | def forward(gy) 65 | if @use_cudnn 66 | return _forward_cudnn(gy[0]) 67 | end 68 | 69 | h, w = @in_shape[2..-1] 70 | shape = gy[0].shape 71 | shape.insert(2, 1, 1) 72 | gcol = gy[0].reshape(*shape).tile(1, 1, @kh, @kw, 1, 1) 73 | 74 | gx = Chainer::Utils::Conv.col2im(gcol, @sy, @sx, @ph, @pw, h, w) 75 | gx /= @kh * @kw 76 | [gx] 77 | end 78 | 79 | private def _forward_cudnn(gy) 80 | x = @apool2d.get_retained_inputs.first.data 81 | y = @apool2d.get_retained_outputs.first.data 82 | gx = x.avg_pool_backward(y, gy, [@kh, @kw], stride: [@sy, @sx], pad: [@ph, @pw], pad_value: 0) 83 | return [gx] 84 | end 85 | 86 | def backward(indexes, grad_outputs) 87 | AveragePooling2D.new([@kh, @kw], stride: [@sy, @sx], pad: [@ph, @pw], cover_all: false).apply(grad_outputs) 88 | end 89 | end 90 | end 91 | end 92 | end 93 | -------------------------------------------------------------------------------- /lib/chainer/functions/pooling/pooling_2d.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Functions 3 | module Pooling 4 | # Base class of pooling function over a set of 2d planes 5 | class Pooling2D < Chainer::FunctionNode 6 | attr_reader :kh, :kw, :sy, :sx, :ph, :pw, :cover_all, :use_cudnn 7 | 8 | def initialize(ksize, stride: nil, pad: 0, cover_all: true) 9 | if stride.nil? 10 | stride = ksize 11 | end 12 | 13 | @kh, @kw = ksize.is_a?(::Array) ? ksize : [ksize, ksize] 14 | @sy, @sx = stride.is_a?(::Array) ? stride : [stride, stride] 15 | @ph, @pw = pad.is_a?(::Array) ? pad: [pad, pad] 16 | 17 | @cover_all = cover_all 18 | @use_cudnn = false 19 | end 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/chainer/gradient_method.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | class GradientMethod < Chainer::Optimizer 3 | def initialize 4 | super() 5 | @hyperparam = Hyperparameter.new 6 | end 7 | 8 | def setup(link) 9 | super(link) 10 | link.params do |param| 11 | param.update_rule = create_update_rule 12 | end 13 | end 14 | 15 | def reallocate_cleared_grads 16 | @target.namedparams(include_uninit: false) do |(name, param)| 17 | if param.grad.nil? 18 | xm = Chainer.get_array_module(param.data) 19 | param.grad = xm::NArray.[](*param.data).new_zeros 20 | end 21 | end 22 | end 23 | 24 | def call_hooks 25 | @hooks.values.each do |hook| 26 | _call_hook(hook) 27 | reallocate_cleared_grads 28 | end 29 | end 30 | 31 | def update(lossfun=nil, *args, **kwds) 32 | if lossfun 33 | use_cleargrads = self.methods.include?(:use_cleargrads) ? self.use_cleargrads : true 34 | if args.size > 0 && kwds.keys.size > 0 35 | loss = lossfun.(*args, **kwds) 36 | elsif args.size > 0 37 | loss = lossfun.(*args) 38 | elsif kwds.keys.size > 0 39 | loss = lossfun.(**kwds) 40 | end 41 | 42 | if use_cleargrads 43 | @target.cleargrads() 44 | else 45 | @target.zerograds() 46 | end 47 | loss.backward() 48 | end 49 | 50 | reallocate_cleared_grads 51 | 52 | call_hooks 53 | 54 | @t += 1 55 | @target.params do |param| 56 | param.update 57 | end 58 | end 59 | 60 | def create_update_rule 61 | raise NotImplementedError 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /lib/chainer/hyperparameter.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | class Hyperparameter 3 | attr_reader :parent 4 | 5 | def initialize(parent: nil) 6 | @parent = parent 7 | end 8 | 9 | def method_missing(name) 10 | @parent.instance_variable_get("@#{name}") 11 | end 12 | 13 | def get_dict 14 | d = @parent.nil? ? {} : @parent.get_dict 15 | self.instance_variables.each do |m| 16 | unless m == :@parent 17 | d[m.to_s.delete('@')] = self.instance_variable_get(m) 18 | end 19 | end 20 | d 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/chainer/initializer.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | class Initializer 3 | attr_accessor :dtype 4 | 5 | def initialize(dtype: nil) 6 | @dtype = dtype 7 | end 8 | 9 | def call(array) 10 | raise NotImplementedError 11 | end 12 | end 13 | end 14 | 15 | -------------------------------------------------------------------------------- /lib/chainer/initializers/constant.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Initializers 3 | class Constant < ::Chainer::Initializer 4 | def initialize(fill_value, dtype: nil) 5 | @fill_value = fill_value 6 | super(dtype: dtype) 7 | end 8 | 9 | def call(array) 10 | if @dtype 11 | raise ArgumentError unless array.class == @dtype 12 | end 13 | array.store(@fill_value) 14 | array 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/chainer/initializers/init.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Initializers 3 | def self.generate_array(initializer, shape, device: Chainer::Device.default) 4 | klass = device.xm::SFloat 5 | if initializer.respond_to?(:dtype) && initializer.dtype 6 | klass = initializer.dtype 7 | end 8 | array = klass.new(shape).rand 9 | initializer.(array) 10 | end 11 | 12 | def self.get_initializer(initializer, device: Chainer::Device.default) 13 | return HeNormal.new(scale: 1 / device.xm::NMath.sqrt(2)) if initializer.nil? 14 | return Constant.new(initializer) if initializer.kind_of?(Numeric) 15 | return Constant.new(initializer) if Chainer.array?(initializer) 16 | 17 | unless initializer.respond_to?(:call) 18 | raise TypeError, "invalid type of initializer: #{initializer.class}" 19 | end 20 | 21 | return initializer 22 | end 23 | 24 | def self.nan(dtype: nil) 25 | Constant.new(Float::NAN, dtype: dtype) 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/chainer/initializers/normal.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Initializers 3 | class Normal < ::Chainer::Initializer 4 | def initialize(scale: 0.05, dtype: nil) 5 | @scale = scale 6 | super(dtype: dtype) 7 | end 8 | 9 | def call(array) 10 | args = { loc: 0.0, scale: @scale, size: array.shape} 11 | array.class.new(array.shape).rand_norm(0.0, @scale) 12 | end 13 | end 14 | 15 | class HeNormal < ::Chainer::Initializer 16 | def initialize(scale: 1.0, dtype: nil) 17 | @scale = scale 18 | super(dtype: dtype) 19 | end 20 | 21 | def call(array) 22 | # TODO(sonots): pass device from outside 23 | device = Chainer::Device.default 24 | fan_in, fan_out = Chainer::Utils::Initializer.get_fans(array.shape, device: device) 25 | s = @scale * device.xm::NMath.sqrt(2.0 / fan_in) 26 | Normal.new(scale: s).(array) 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/chainer/initializers/uniform.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Initializers 3 | class Uniform < ::Chainer::Initializer 4 | def initialize(scale: 0.05, dtype: nil) 5 | @scale = scale 6 | super(dtype: dtype) 7 | end 8 | 9 | def call(array) 10 | raise ArgumentError.new("dtypes are missmatched. #{dtype} != #{array.class}") if dtype && dtype != array.class 11 | array.class.new(array.shape).rand(-@scale, @scale) 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/chainer/iterators/serial_iterator.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Iterators 3 | class SerialIterator < Chainer::Dataset::Iterator 4 | attr_reader :epoch, :is_new_epoch 5 | 6 | def initialize(dataset, batch_size, repeat: true, shuffle: true, device: Chainer::Device.default) 7 | @dataset = dataset 8 | @batch_size = batch_size 9 | @repeat = repeat 10 | @shuffle = shuffle 11 | @device = device 12 | @xm = device.xm 13 | 14 | reset 15 | end 16 | 17 | def next 18 | raise StopIteration if !@repeat && @epoch > 0 19 | 20 | @previous_epoch_detail = epoch_detail 21 | 22 | i = @current_position 23 | n = @dataset.size 24 | i_end = [i + @batch_size, n].min 25 | 26 | batch = @order[i...i_end].to_a.map { |index| @dataset[index] } 27 | 28 | if i_end >= n 29 | if @repeat 30 | rest = i_end - n 31 | unless @order.nil? 32 | @order = @order.class[*@order.to_a.shuffle] 33 | end 34 | if rest > 0 35 | if @order.nil? 36 | batch = batch.append(@dataset[0...rest]) 37 | else 38 | batch = @dataset[0...rest].map { |index| @dataset[index] } 39 | end 40 | end 41 | @current_position = rest 42 | else 43 | @current_position = 0 44 | end 45 | 46 | @epoch += 1 47 | @is_new_epoch = true 48 | else 49 | @is_new_epoch = false 50 | @current_position = i_end 51 | end 52 | 53 | batch 54 | end 55 | 56 | def epoch_detail 57 | @epoch + @current_position.to_f / @dataset.size 58 | end 59 | 60 | def serialize(serializer) 61 | @current_position = serializer.('current_position', @current_position) 62 | @epoch = serializer.('epoch', @epoch) 63 | @is_new_epoch = serializer.('is_new_epoch', @is_new_epoch) 64 | unless @order.nil? 65 | begin 66 | serializer.('order', @order) 67 | rescue KeyError 68 | serializer('_order', @order) 69 | end 70 | end 71 | 72 | begin 73 | @previous_epoch_detail = serializer.( 'previous_epoch_detail', @previous_epoch_detail) 74 | rescue KeyError 75 | # guess previous_epoch_detail for older version 76 | @previous_epoch_detail = @epoch + (@current_position - @batch_size) / @dataset.size 77 | if epoch_detail > 0 78 | @previous_epoch_detail = [@previous_epoch_detail, 0.0].max 79 | else 80 | @previous_epoch_detail = -1.0 81 | end 82 | end 83 | end 84 | 85 | def reset 86 | if @shuffle 87 | order = @dataset.size.times.map(&:to_i).shuffle 88 | @order = @xm::Int64[*order] 89 | else 90 | order = @dataset.size.times.map(&:to_i) 91 | @order = @xm::Int64[*order] 92 | end 93 | 94 | @current_position = 0 95 | @epoch = 0 96 | @is_new_epoch = false 97 | @previous_epoch_detail = -1.0 98 | end 99 | end 100 | end 101 | end 102 | -------------------------------------------------------------------------------- /lib/chainer/links/connection/convolution_2d.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Links 3 | module Connection 4 | class Convolution2D < ::Chainer::Link 5 | # Two-dimensional convolutional layer. 6 | # 7 | # This link wraps the :func:`chainer.functions.convolution_2d` function 8 | # and holds the filter weight and bias vector as parameters. 9 | # 10 | # @param [integer or nil] in_channels Number of channels of input arrays. 11 | # If `nil`, parameter initialization will be deferred until the first forward data pass at which time the size will be determined. 12 | # @param [integer] out_channels Number of channels of output arrays. 13 | # @param [integer or 2-d int array] ksize Size of filters (a.k.a. kernels). 14 | # @param [integer or 2-d int array] stride Stride of filter applications. 15 | # @param [integer or 2-d int array] pad Spatial padding width for input arrays. 16 | # @param [boolean] nobias If `true`, then this link does not use the bias term. 17 | # @param [Numo::NArray or Cumo::NArray] initial_w Initial weight value. If `nil`, the default initializer is used. 18 | # @param [Numo::NArray or Cumo::NArray] initial_bias Initial bias value. If `nil`, the bias is set to 0. 19 | # 20 | # Example 21 | # There are several ways to make a Convolution2D link. 22 | # Let an input vector `x` be: 23 | # > x = Numo::DFloat.new(1, 3, 10, 10).seq 24 | # 25 | # 1. Give the first three arguments explicitly: 26 | # > l = Chainer::Links::Connection::Convolution2D.new(3, 7, 5) 27 | # > y = l.(x) 28 | # > y.shape 29 | # [1, 7, 6, 6] 30 | # 31 | # 2. Omit `in_channels` or fill it with `nil`: 32 | # The below two cases are the same. 33 | # 34 | # > l = Chainer::Links::Connection::Convolution2D.new(7, 5) 35 | # > y = l.(x) 36 | # > y.shape 37 | # [1, 7, 6, 6] 38 | # 39 | # > l = Chainer::Links::Connection::Convolution2D.new(nil, 7, 5) 40 | # > y = l.(x) 41 | # > y.shape 42 | # [1, 7, 6, 6] 43 | # 44 | # When you omit the first argument, you need to specify the other subsequent arguments from `stride` as keyword auguments. 45 | # 46 | # > l = Chainer::Links::Connection::Convolution2D.new(7, 5, stride: 1, pad: 0) 47 | # > y = l.(x) 48 | # > y.shape 49 | # [1, 7, 6, 6] 50 | def initialize(in_channels, out_channels, ksize=nil, stride: 1, pad: 0, nobias: false, initial_w: nil, initial_bias: nil) 51 | super() 52 | if ksize.nil? 53 | out_channels, ksize, in_channels = in_channels, out_channels, nil 54 | end 55 | 56 | @ksize = ksize 57 | @stride = stride.is_a?(Array) ? stride : [stride, stride] 58 | @pad = pad.is_a?(Array) ? pad : [pad, pad] 59 | @out_channels = out_channels 60 | 61 | init_scope do 62 | w_initializer = Chainer::Initializers.get_initializer(initial_w) 63 | @w = Chainer::Parameter.new(initializer: w_initializer) 64 | if in_channels 65 | initialize_params(in_channels) 66 | end 67 | 68 | if nobias 69 | @b = nil 70 | else 71 | initial_bias = 0 if initial_bias.nil? 72 | bias_initializer = Chainer::Initializers.get_initializer(initial_bias) 73 | @b = Chainer::Parameter.new(initializer: bias_initializer, shape: out_channels) 74 | end 75 | end 76 | end 77 | 78 | # Applies the convolution layer. 79 | # @param [Chainer::Variable] x Input image. 80 | # @return [Chainer::Variable] Output of the convolution. 81 | def call(x) 82 | initialize_params(x.shape[1]) if @w.data.nil? 83 | Chainer::Functions::Connection::Convolution2DFunction.convolution_2d(x, @w, b: @b, stride: @stride, pad: @pad) 84 | end 85 | 86 | private 87 | 88 | def initialize_params(in_channels) 89 | kh, kw = @ksize.is_a?(Array) ? @ksize : [@ksize, @ksize] 90 | w_shape = [@out_channels, in_channels, kh, kw] 91 | @w.init(w_shape) 92 | end 93 | end 94 | end 95 | end 96 | end 97 | 98 | 99 | -------------------------------------------------------------------------------- /lib/chainer/links/connection/embed_id.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Links 3 | module Connection 4 | class EmbedID < ::Chainer::Link 5 | attr_reader :w 6 | 7 | def initialize(in_size, out_size, initial_w: nil, ignore_label: nil) 8 | super() 9 | @ignore_label = ignore_label 10 | 11 | init_scope do 12 | initial_w ||= Chainer::Initializers::Normal.new(scale: 1.0) 13 | @w = Chainer::Parameter.new(initializer: initial_w, shape: [in_size, out_size]) 14 | end 15 | end 16 | 17 | def call(x) 18 | Chainer::Functions::Connection::EmbedIDFunction.embed_id(x, @w, ignore_label: @ignore_label) 19 | end 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/chainer/links/connection/linear.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Links 3 | module Connection 4 | class Linear < ::Chainer::Link 5 | attr_reader :w, :b 6 | 7 | def initialize(in_size, out_size: nil, nobias: false, initial_w: nil, initial_bias: nil) 8 | super() 9 | in_size, out_size = nil, in_size if out_size.nil? 10 | @out_size = out_size 11 | 12 | init_scope do 13 | w_initializer = Chainer::Initializers.get_initializer(initial_w) 14 | @w = Chainer::Parameter.new(initializer: w_initializer) 15 | 16 | initialize_params(in_size) unless in_size.nil? 17 | 18 | if nobias 19 | @b = nil 20 | else 21 | initial_bias = 0 if initial_bias.nil? 22 | bias_initializer = Chainer::Initializers.get_initializer(initial_bias) 23 | @b = Chainer::Parameter.new(initializer: bias_initializer, shape: out_size) 24 | end 25 | end 26 | end 27 | 28 | def call(x) 29 | if @w.data.nil? 30 | initialize_params(x.size.div(x.shape[0])) 31 | end 32 | Chainer::Functions::Connection::LinearFunction.linear(x, @w, @b) 33 | end 34 | 35 | private 36 | 37 | def initialize_params(in_size) 38 | @w.init([@out_size, in_size]) 39 | end 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/chainer/links/model/classifier.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Links 3 | module Model 4 | class Classifier < Chain 5 | attr_accessor :compute_accuracy 6 | 7 | # @param [Chainer::Link] predictor Predictor network. 8 | # @param [Function] lossfun Loss function. 9 | # @param [Function] accfun Function that computes accuracy. 10 | # @param [Integer, String] label_key Key to specify label variable from arguments. 11 | # When it is Integer, a variable in positional arguments is used. 12 | # And when it is String, a variable in keyword arguments is used. 13 | def initialize(predictor, lossfun=Functions::Loss::SoftmaxCrossEntropy.method(:softmax_cross_entropy), accfun=Functions::Evaluation::Accuracy.method(:accuracy), label_key=-1) 14 | super() 15 | 16 | unless label_key.is_a?(Integer) || label_key.is_a?(String) 17 | raise TypeError, "label_key must be Integer or String, but is #{label_key.class}" 18 | end 19 | 20 | @lossfun = lossfun 21 | @accfun = accfun 22 | @y = nil 23 | @loss = nil 24 | @accuracy = nil 25 | @compute_accuracy = true 26 | @label_key = label_key 27 | 28 | init_scope do 29 | @predictor = predictor 30 | end 31 | end 32 | 33 | def call(*args, **kwargs) 34 | if @label_key.is_a?(Integer) 35 | raise IndexError, "label_key #{@label_key} is out of bounds" if @label_key < -args.size || @label_key >= args.size 36 | t = args.slice!(@label_key) 37 | elsif @label_key.is_a?(String) 38 | raise KeyError, "label_key #{@label_key} is not found" unless kwargs.has_key?(@label_key) 39 | t = kwargs[@label_key] 40 | kwargs.delete(@label_key) 41 | end 42 | 43 | @y = nil 44 | @accuracy = nil 45 | @y = @predictor.(*args, **kwargs) 46 | 47 | @loss = @lossfun.call(@y, t) 48 | Chainer::Reporter.save_report({loss: @loss}, self) 49 | if @compute_accuracy 50 | @accuracy = @accfun.call(@y, t) 51 | Chainer::Reporter.save_report({accuracy: @accuracy}, self) 52 | end 53 | @loss 54 | end 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/chainer/optimizers/adam.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Optimizers 3 | class AdamRule < UpdateRule 4 | def initialize(parent_hyperparam: nil, alpha: nil, beta1: nil, beta2: nil, eps: nil) 5 | hyperparam = Hyperparameter.new 6 | hyperparam.instance_variable_set('@alpha', 0.001) 7 | hyperparam.instance_variable_set('@beta1', 0.9) 8 | hyperparam.instance_variable_set('@beta2', 0.999) 9 | hyperparam.instance_variable_set('@eps', 1e-8) 10 | 11 | super(parent_hyperparam: parent_hyperparam || hyperparam) 12 | 13 | @hyperparam.instance_variable_set('@alpha', alpha) if alpha 14 | @hyperparam.instance_variable_set('@beta1', beta1) if beta1 15 | @hyperparam.instance_variable_set('@beta2', beta2) if beta2 16 | @hyperparam.instance_variable_set('@eps', eps) if eps 17 | end 18 | 19 | def init_state(param) 20 | @state[:m] = param.data.new_zeros 21 | @state[:v] = param.data.new_zeros 22 | end 23 | 24 | def update_core(param) 25 | grad = param.grad 26 | return if grad.nil? 27 | 28 | hp = @hyperparam 29 | 30 | @state[:m] += (1 - hp.beta1) * (grad - @state[:m]) 31 | @state[:v] += (1 - hp.beta2) * (grad * grad - @state[:v]) 32 | xm = Chainer.get_array_module(grad) 33 | param.data -= lr * @state[:m] / (xm::NMath.sqrt(@state[:v]) + hp.eps) 34 | end 35 | 36 | def lr 37 | fix1 = 1.0 - @hyperparam.beta1 ** @t 38 | fix2 = 1.0 - @hyperparam.beta2 ** @t 39 | @hyperparam.alpha * Math.sqrt(fix2) / fix1 40 | end 41 | end 42 | 43 | class Adam < GradientMethod 44 | def initialize(alpha: nil, beta1: nil, beta2: nil, eps: nil) 45 | super() 46 | @hyperparam.instance_variable_set('@alpha', alpha || 0.001) 47 | @hyperparam.instance_variable_set('@beta1', beta1 || 0.9) 48 | @hyperparam.instance_variable_set('@beta2', beta2 || 0.999) 49 | @hyperparam.instance_variable_set('@eps', eps || 1e-8) 50 | end 51 | 52 | def create_update_rule 53 | AdamRule.new(parent_hyperparam: @hyperparam) 54 | end 55 | 56 | def lr 57 | fix1 = 1.0 - (@hyperparam.beta1 ** @t) 58 | fix2 = 1.0 - (@hyperparam.beta2 ** @t) 59 | @hyperparam.alpha * Math.sqrt(fix2) / fix1 60 | end 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /lib/chainer/optimizers/momentum_sgd.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Optimizers 3 | # Update rule for the classical momentum SGD 4 | class MomentumSGDRule < UpdateRule 5 | def initialize(parent_hyperparam: nil, lr: nil, mementum: nil) 6 | hyperparam = Hyperparameter.new 7 | hyperparam.instance_variable_set('@lr', 0.01) 8 | hyperparam.instance_variable_set('@momentum', 0.9) 9 | 10 | super(parent_hyperparam: parent_hyperparam || hyperparam) 11 | 12 | @hyperparam.instance_variable_set('@lr', lr) if lr 13 | @hyperparam.instance_variable_set('@mementum', mementum) if mementum 14 | end 15 | 16 | def init_state(param) 17 | @state[:v] = param.data.new_zeros 18 | end 19 | 20 | def update_core(param) 21 | grad = param.grad 22 | return if grad.nil? 23 | 24 | v = @state[:v] 25 | v *= @hyperparam.momentum 26 | v -= @hyperparam.lr * grad 27 | param.data += v 28 | end 29 | end 30 | 31 | # Momentum SGD optimizer 32 | class MomentumSGD < GradientMethod 33 | attr_accessor :lr, :momentum 34 | # @param [Float] lr Learning rate 35 | # @param [Float] momentum Exponential decay rate of the first order moment 36 | def initialize(lr: nil, momentum: nil) 37 | super() 38 | @hyperparam.instance_variable_set('@lr', lr || 0.01) 39 | @hyperparam.instance_variable_set('@momentum', momentum || 0.9) 40 | Chainer::HyperparameterProxy.new(self, "lr") 41 | Chainer::HyperparameterProxy.new(self, "momentum") 42 | end 43 | 44 | def create_update_rule 45 | MomentumSGDRule.new(parent_hyperparam: @hyperparam) 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/chainer/parameter.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | class Parameter < Variable 3 | attr_accessor :initializer, :grad_initializer, :update_rule 4 | 5 | def initialize(initializer: nil, shape: nil, name: nil) 6 | if initializer.nil? 7 | initializer = Chainer::Initializers.nan() 8 | elsif initializer.kind_of?(Numeric) 9 | initializer = Initializers::Constant.new(initializer) 10 | end 11 | 12 | if shape.nil? 13 | if Chainer.array?(initializer) 14 | super(initializer, name: name) 15 | else 16 | super(name: name) 17 | @initializer = initializer 18 | dtype = initializer.respond_to?(:dtype) ? initializer.dtype : 'SFloat' 19 | @grad_initializer = Chainer::Initializers.nan(dtype: dtype) 20 | end 21 | else 22 | if Chainer.array?(initializer) 23 | initializer = Initializers::Constant.new(initializer) 24 | end 25 | data = Chainer::Initializers.generate_array(initializer, shape) 26 | xm = Chainer.get_array_module(data) 27 | grad = xm::NArray[*[1, 2]].new_fill(-922337203) 28 | super(data, name: name, grad: grad) 29 | end 30 | 31 | @update_rule = nil 32 | end 33 | 34 | def cleargrad 35 | super 36 | @grad_initializer = nil if self.data.nil? 37 | end 38 | 39 | def init(shape) 40 | data = Chainer::Initializers.generate_array(@initializer, shape) 41 | ginit = @grad_initializer 42 | grad = ginit.nil? ? nil : Chainer::Initializers.generate_array(ginit, shape) 43 | 44 | self.data = data 45 | self.grad = grad 46 | end 47 | 48 | def update 49 | if @update_rule 50 | @update_rule.update(self) 51 | end 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/chainer/serializer.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | # Abstract base class of all serializers and deserializers. 3 | class AbstractSerializer 4 | # Gets a child serializer. 5 | # This operator creates a child serializer represented by the given key. 6 | # 7 | # @param [string] key Name of the child serializer. 8 | def [](key) 9 | raise NotImplementedError 10 | end 11 | 12 | # Serializes or deserializes a value by given name. 13 | # This operator saves or loads a value by given name. 14 | # If this is a serializer, then the value is simply saved at the key. 15 | # Note that some type information might be missed depending on the 16 | # implementation (and the target file format). 17 | # If this is a deserializer, then the value is loaded by the key. 18 | # The deserialization differently works on scalars and arrays. 19 | # For scalars, the ``value`` argument is used just for determining the type of 20 | # restored value to be converted, and the converted value is returned. 21 | # For arrays, the restored elements are directly copied into the 22 | # ``value`` argument. String values are treated like scalars. 23 | # 24 | # @param [string] key Name of the serialization entry. 25 | # @param [any] value Object to be (de)serialized. 26 | # ``None`` is only supported by deserializers. 27 | # @return Serialized or deserialized value. 28 | def call(key, value) 29 | raise NotImplementedError 30 | end 31 | end 32 | 33 | # Base class of all serializers. 34 | class Serializer < AbstractSerializer 35 | # Saves an object by this serializer. 36 | # This is equivalent to ``obj.serialize(self)``. 37 | # 38 | # @param [any] obj Target object to be serialized. 39 | def save(obj) 40 | obj.serialize(self) 41 | end 42 | end 43 | 44 | # Base class of all deserializers. 45 | class Deserializer < AbstractSerializer 46 | def load(obj) 47 | obj.serialize(self) 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/chainer/serializers/marshal.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Serializers 3 | class MarshalSerializer < Chainer::Serializer 4 | attr_accessor :target, :path 5 | 6 | # @param [string] file_path Target file path 7 | # @param [Object] obj Object to be serialized 8 | def self.save_file(file_path, obj) 9 | s = self.new 10 | s.save(obj) 11 | File.open(file_path, 'wb') do |f| 12 | Marshal.dump(s.target, f) 13 | end 14 | end 15 | 16 | def initialize(target: nil, path: "", device: Chainer::Device.default) 17 | @target = target.nil? ? {} : target 18 | @path = path 19 | @device = device 20 | @xm = device.xm 21 | end 22 | 23 | def [](key) 24 | self.class.new(target: @target, path: File.join(@path, key, '/')) 25 | end 26 | 27 | def call(key, value) 28 | ret = value 29 | if value.is_a?(TrueClass) 30 | arr = @xm::Bit[1] 31 | elsif value.is_a?(FalseClass) 32 | arr = @xm::Bit[0] 33 | elsif value.instance_of?(String) || value.nil? 34 | arr = value 35 | else 36 | arr = @xm::NArray.cast(value) 37 | end 38 | @target[File.join(@path, key)] = arr 39 | ret 40 | end 41 | end 42 | 43 | class MarshalDeserializer < Chainer::Deserializer 44 | # Loads an object from the file in Marshal format. 45 | # This is a short-cut function to load from an Marshal file that contains only one object. 46 | # 47 | # @param [string] filename Name of the file to be loaded. 48 | # @param [object] obj Object to be deserialized. It must support serialization protocol. 49 | def self.load_file(filename, obj, path: '', strict: true) 50 | File.open(filename) do |f| 51 | d = self.new(Marshal.load(f), path: path, strict: strict) 52 | d.load(obj) 53 | end 54 | end 55 | 56 | def initialize(marshalData, path: '', strict: true) 57 | @marshal_data = marshalData 58 | @path = path 59 | @strict = strict 60 | end 61 | 62 | def [](key) 63 | self.class.new(@marshal_data, path: File.join(@path, key, '/'), strict: @strict) 64 | end 65 | 66 | def call(key, value) 67 | key = File.join(@path, key) 68 | if !@strict && !@marshal_data.keys.include?(key) 69 | return value 70 | end 71 | 72 | dataset = @marshal_data[key] 73 | if value.nil? 74 | return dataset 75 | elsif value.instance_of?(String) 76 | return dataset 77 | elsif Chainer.array?(value) 78 | value.store(dataset) 79 | return value 80 | elsif value.is_a?(TrueClass) || value.is_a?(FalseClass) 81 | return dataset[0] == 1 82 | else 83 | return dataset[0] 84 | end 85 | end 86 | end 87 | end 88 | end 89 | 90 | -------------------------------------------------------------------------------- /lib/chainer/testing/array.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Chainer 4 | module Testing 5 | def assert_allclose(expect, actual, atol: 1e-5, rtol: 1e-4) 6 | # Asserts if some corresponding element of x and y differs too much. 7 | # 8 | # This function can handle both CPU and GPU arrays simultaneously. 9 | # 10 | # Args: 11 | # expect: Left-hand-side array. 12 | # actual: Right-hand-side array. 13 | # atol (float): Absolute tolerance. 14 | # rtol (float): Relative tolerance. 15 | # 16 | expect = Utils::Array.force_array(expect) 17 | actual = Utils::Array.force_array(actual) 18 | 19 | # If the expected is 0-dim arrary, extend the dimension to the actual. 20 | if (expect.shape != actual.shape) and (expect.ndim == 0) 21 | expect = actual.class.new(actual.shape).fill(expect.to_f) 22 | end 23 | 24 | actual.each_with_index{|actual_val, *i| 25 | if (expect[*i].to_f - actual_val.to_f).abs > atol + rtol * expect[*i].to_f.abs 26 | raise "assert_allclose Error\n expect: #{expect.inspect}\n actual : #{actual.inspect}\n (#{i})=> #{(expect - actual).abs.max()} > #{atol + rtol * expect[*i].abs}" 27 | end 28 | } 29 | end 30 | module_function :assert_allclose 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/chainer/training/extension.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Training 3 | class Extension 4 | PRIORITY_WRITER = 300 5 | PRIORITY_EDITOR = 200 6 | PRIORITY_READER = 100 7 | 8 | attr_accessor :name 9 | attr_writer :trigger, :priority 10 | 11 | def initialize 12 | end 13 | 14 | def call(trainer) 15 | end 16 | 17 | def default_name 18 | self.class.name.split('::').last 19 | end 20 | 21 | def trigger 22 | @trigger || [1, 'iteration'] 23 | end 24 | 25 | def priority 26 | @priority || PRIORITY_READER 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/chainer/training/extensions/exponential_shift.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Training 3 | module Extensions 4 | # Trainer extension to exponentially shift an optimizer attribute. 5 | # 6 | # This extension exponentially increases or decreases the specified attribute of the optimizer. 7 | # The typical use case is an exponential decay of the learning rate. 8 | # This extension is also called before the training loop starts by default. 9 | class ExponentialShift < Extension 10 | attr_reader :last_value 11 | 12 | # @param [string] attr Name of the attribute to shift 13 | # @param [float] rate Rate of the exponential shift. 14 | # @param [float] init Initial value of the attribute. 15 | # @param [float] target Target value of the attribute. 16 | # @param [Chainer::Optimizer] optimizer Target optimizer to adjust the attribute. 17 | def initialize(attr, rate, init: nil, target: nil, optimizer: nil) 18 | @attr = attr 19 | raise 'ExponentialShift does not support negative rate' if rate < 0 20 | @rate = rate 21 | @init = init 22 | @target = target 23 | @optimizer = optimizer 24 | @t = 0 25 | @last_value = nil 26 | end 27 | 28 | def init(trainer) 29 | optimizer = get_optimizer(trainer) 30 | @init = optimizer.send(@attr) if @init.nil? 31 | if @last_value.nil? 32 | update_value(optimizer, @init) 33 | else 34 | update_value(optimizer, @last_value) 35 | end 36 | end 37 | 38 | def call(trainer) 39 | @t += 1 40 | 41 | optimizer = get_optimizer(trainer) 42 | value = @init * (@rate ** @t) 43 | if @target 44 | if @rate > 1 45 | if value / @target > 1 46 | value = @target 47 | end 48 | else 49 | if value / @target < 1 50 | value = @target 51 | end 52 | end 53 | end 54 | update_value(optimizer, value) 55 | end 56 | 57 | def serialize(serializer) 58 | @t = serializer.('t', @t) 59 | @last_value = serializer.('last_value', @last_value) 60 | if Chainer.array?(@last_value) 61 | @last_value = @last_value[0] 62 | end 63 | end 64 | 65 | private 66 | 67 | def get_optimizer(trainer) 68 | @optimizer || trainer.updater.get_optimizer(:main) 69 | end 70 | 71 | def update_value(optimizer, value) 72 | optimizer.send("#{@attr}=", value) 73 | @last_value = value 74 | end 75 | end 76 | end 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /lib/chainer/training/extensions/log_report.rb: -------------------------------------------------------------------------------- 1 | require 'tempfile' 2 | require 'json' 3 | 4 | module Chainer 5 | module Training 6 | module Extensions 7 | class LogReport < Extension 8 | attr_reader :log 9 | 10 | def initialize(keys: nil, trigger: [1, 'epoch'], postprocess: nil, log_name: 'log') 11 | @keys = keys 12 | @_trigger = Chainer::Training::Util.get_trigger(trigger) 13 | @postprocess = postprocess 14 | @log_name = log_name 15 | @log = [] 16 | 17 | init_summary 18 | end 19 | 20 | def call(trainer) 21 | observation = trainer.observation 22 | 23 | if @keys.nil? 24 | @summary.add(observation) 25 | else 26 | symbolized_observation = Hash[observation.map{|(k,v)| [k.to_sym,v]}] 27 | filterd_keys = @keys.select {|k| observation.keys.include?(k.to_sym) } 28 | @summary.add(filterd_keys.each_with_object({}) {|k, hash| hash[k.to_s] = observation[k.to_sym] }) 29 | end 30 | 31 | # if @_trigger is true, output the result 32 | return unless @_trigger.(trainer) 33 | 34 | stats = @summary.compute_mean 35 | stats_cpu = {} 36 | stats.each do |name, value| 37 | stats_cpu[name] = value.to_f # copy to CPU 38 | end 39 | 40 | updater = trainer.updater 41 | stats_cpu['epoch'] = updater.epoch 42 | stats_cpu['iteration'] = updater.iteration 43 | stats_cpu['elapsed_time'] = trainer.elapsed_time 44 | 45 | @postprocess.(stats_cpu) unless @postprocess.nil? 46 | 47 | @log << stats_cpu 48 | 49 | unless @log_name.nil? 50 | # example: sprintf("%{a}, %{b}", {a: "1", b: "2"}) 51 | # => "1, 2" 52 | log_name = sprintf(@log_name, stats_cpu) 53 | temp_file = Tempfile.create(basename: log_name, tmpdir: trainer.out) 54 | 55 | JSON.dump(@log, temp_file) 56 | 57 | new_path = File.join(trainer.out, log_name) 58 | FileUtils.mv(temp_file.path, new_path) 59 | end 60 | 61 | init_summary 62 | end 63 | 64 | def serialize(serializer) 65 | if @_trigger.respond_to?(:serialize) 66 | @_trigger.serialize(serializer['_trigger']) 67 | end 68 | # Note that this serialization may lose some information of small 69 | # numerical differences. 70 | if serializer.is_a?(Chainer::Serializer) 71 | log = JSON.generate(@log) 72 | serializer.('_log', log) 73 | else 74 | log = serializer.('_log', '') 75 | @log = JSON.parse(log) 76 | end 77 | end 78 | 79 | private 80 | 81 | def init_summary 82 | @summary = DictSummary.new 83 | end 84 | end 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /lib/chainer/training/extensions/print_report.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Training 3 | module Extensions 4 | class PrintReport < Extension 5 | def initialize(entries, log_report: 'LogReport', out: STDOUT) 6 | @entries = entries 7 | @log_report = log_report 8 | @out = out 9 | 10 | @log_len = 0 # number of observations already printed 11 | 12 | # format information 13 | entry_widths = entries.map { |s| [10, s.size].max } 14 | 15 | templates = [] 16 | header = [] 17 | entries.zip(entry_widths).each do |entry, w| 18 | header << sprintf("%-#{w}s", entry) 19 | templates << [entry, "%-#{w}g ", ' ' * (w + 2)] 20 | end 21 | @header = header.join(' ') + "\n" 22 | @templates = templates 23 | end 24 | 25 | def call(trainer) 26 | if @header 27 | @out.write(@header) 28 | @header = nil 29 | end 30 | 31 | if @log_report.is_a?(String) 32 | log_report = trainer.get_extension(@log_report) 33 | elsif @log_report.is_a?(LogReport) 34 | log_report.(trainer) 35 | else 36 | raise TypeError, "log report has a wrong type #{log_report.class}" 37 | end 38 | 39 | log = log_report.log 40 | while log.size > @log_len 41 | @out.write("\033[J") 42 | print(log[@log_len]) 43 | @log_len += 1 44 | end 45 | end 46 | 47 | def serialize(serializer) 48 | if @log_report.is_a?(Chainer::Training::Extensions::LogReport) 49 | @log_report.serialize(serializer['_log_report']) 50 | end 51 | end 52 | 53 | private 54 | 55 | def print(observation) 56 | @templates.each do |entry, template, empty| 57 | if observation.keys.include?(entry) 58 | @out.write(sprintf(template, observation[entry])) 59 | else 60 | @out.write(empty) 61 | end 62 | end 63 | @out.write("\n") 64 | end 65 | end 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /lib/chainer/training/extensions/progress_bar.rb: -------------------------------------------------------------------------------- 1 | require 'erb' 2 | require 'time' 3 | 4 | module Chainer 5 | module Training 6 | module Extensions 7 | class ProgressBar < Extension 8 | def initialize(training_length: nil, update_interval: 100, bar_length: 50, out: STDOUT) 9 | @training_length = training_length 10 | @status_template = nil 11 | @update_interval = update_interval 12 | @bar_length = bar_length 13 | @out = out 14 | @out.sync = true 15 | @recent_timing = [] 16 | end 17 | 18 | def call(trainer) 19 | if @training_length.nil? 20 | t = trainer.stop_trigger 21 | raise TypeError, "cannot retrieve the training length #{t.class}" unless t.is_a?(Chainer::Training::Triggers::IntervalTrigger) 22 | @training_length = [t.period, t.unit] 23 | end 24 | 25 | if @status_template.nil? 26 | @status_template = ERB.new("<%= sprintf('%10d', self.iteration) %> iter, <%= self.epoch %> epoch / #{@training_length[0]} #{@training_length[1]}s\n") 27 | end 28 | 29 | length, unit = @training_length 30 | iteration = trainer.updater.iteration 31 | 32 | # print the progress bar according to interval 33 | return unless iteration % @update_interval == 0 34 | 35 | epoch = trainer.updater.epoch_detail 36 | now = Time.now.to_f 37 | 38 | @recent_timing << [iteration, epoch, now] 39 | @out.write("\033[J") 40 | 41 | if unit == 'iteration' 42 | rate = iteration.to_f / length 43 | else 44 | rate = epoch.to_f / length 45 | end 46 | 47 | marks = '#' * (rate * @bar_length).to_i 48 | @out.write(sprintf(" total [%s%s] %6.2f%\n", marks, '.' * (@bar_length - marks.size), rate * 100)) 49 | 50 | epoch_rate = epoch - epoch.to_i 51 | marks = '#' * (epoch_rate * @bar_length).to_i 52 | @out.write(sprintf("this epoch [%s%s] %6.2f%\n", marks, '.' * (@bar_length - marks.size), epoch_rate * 100)) 53 | 54 | status = @status_template.result(trainer.updater.bind) 55 | @out.write(status) 56 | 57 | old_t, old_e, old_sec = @recent_timing[0] 58 | span = now - old_sec 59 | 60 | if span.zero? 61 | speed_t = Float::INFINITY 62 | speed_e = Float::INFINITY 63 | else 64 | speed_t = (iteration - old_t) / span 65 | speed_e = (epoch - old_e) / span 66 | end 67 | 68 | if unit == 'iteration' 69 | estimated_time = (length - iteration) / speed_t 70 | else 71 | estimated_time = (length - epoch) / speed_e 72 | end 73 | 74 | @out.write(sprintf("%10.5g iters/sec. Estimated time to finish: %s.\n", speed_t, (Time.parse("1991/01/01") + (estimated_time)).strftime("%H:%m:%S"))) 75 | 76 | # move the cursor to the head of the progress bar 77 | @out.write("\033[4A") # TODO: Support Windows 78 | @out.flush 79 | 80 | @recent_timing.delete_at(0) if @recent_timing.size > 100 81 | end 82 | 83 | def finalize 84 | @out.write("\033[J") # TODO: Support Windows 85 | @out.flush 86 | end 87 | end 88 | end 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /lib/chainer/training/extensions/snapshot.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Training 3 | module Extensions 4 | class Snapshot < Extension 5 | attr_accessor :save_class, :filename_proc, :target 6 | 7 | def self.snapshot_object(target:, save_class:, &block) 8 | self.new(save_class: save_class, filename_proc: block, target: target) 9 | end 10 | 11 | def self.snapshot(save_class: nil, &block) 12 | self.new(save_class: save_class, filename_proc: block) 13 | end 14 | 15 | def initialize(save_class: nil, filename_proc: nil, target: nil) 16 | @priority = -100 17 | @trigger = [1, 'epoch'] 18 | @save_class = save_class || Chainer::Serializers::MarshalSerializer 19 | @filename_proc = filename_proc || Proc.new { |trainer| "snapshot_iter_#{trainer.updater.iteration}" } 20 | @target = target 21 | end 22 | 23 | def call(trainer) 24 | target = @target || trainer 25 | filename = filename_proc.call(trainer) 26 | prefix = "tmp#{filename}" 27 | temp_file = Tempfile.create(basename: prefix, tmpdir: trainer.out) 28 | save_class.save_file(temp_file.path, trainer) 29 | FileUtils.move(temp_file.path, File.join(trainer.out, filename)) 30 | end 31 | end 32 | end 33 | end 34 | end 35 | 36 | -------------------------------------------------------------------------------- /lib/chainer/training/standard_updater.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Training 3 | class StandardUpdater < Updater 4 | attr_accessor :iteration 5 | 6 | def initialize(iterator, optimizer, converter: nil, device: nil, loss_func: nil) 7 | if iterator.kind_of?(Dataset::Iterator) 8 | iterator = { main: iterator } 9 | end 10 | @iterators = iterator 11 | 12 | unless optimizer.kind_of?(Hash) 13 | optimizer = { main: optimizer } 14 | end 15 | @optimizers = optimizer 16 | 17 | @converter = converter || Dataset::Convert.method(:concat_examples) 18 | @loss_func = loss_func 19 | @device = device 20 | @iteration = 0 21 | end 22 | 23 | def get_optimizer(name) 24 | @optimizers[name] 25 | end 26 | 27 | def get_all_optimizers 28 | @optimizers.to_h 29 | end 30 | 31 | def update 32 | update_core 33 | @iteration += 1 34 | end 35 | 36 | def epoch 37 | @iterators[:main].epoch 38 | end 39 | 40 | def epoch_detail 41 | @iterators[:main].epoch_detail 42 | end 43 | 44 | def update_core 45 | batch = @iterators[:main].next 46 | in_arrays = @converter.call(batch, device: @device) 47 | 48 | optimizer = @optimizers[:main] 49 | loss_func = @loss_func || optimizer.target 50 | 51 | if in_arrays.kind_of?(Array) 52 | optimizer.update(loss_func, *in_arrays) 53 | elsif in_arrays.kind_of?(Hash) 54 | optimizer.update(loss_func, **in_arrays) 55 | else 56 | optimizer.update(loss_func, in_arrays) 57 | end 58 | end 59 | 60 | def finalize 61 | @iterators.each do |(_, iterator)| 62 | iterator.finalize 63 | end 64 | end 65 | 66 | def serialize(serializer) 67 | @iterators.each do |name, iterator| 68 | iterator.serialize(serializer["iterator:#{name}"]) 69 | end 70 | @optimizers.each do |name, optimizer| 71 | optimizer.serialize(serializer["optimizer:#{name}"]) 72 | optimizer.target.serialize(serializer["model:#{name}"]) 73 | end 74 | 75 | @iteration = serializer.('iteration', @iteration) 76 | end 77 | end 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /lib/chainer/training/triggers/interval.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Training 3 | module Triggers 4 | class IntervalTrigger 5 | attr_reader :period, :unit, :count 6 | 7 | def initialize(period, unit) 8 | @period = period 9 | @unit = unit 10 | @count = 0 11 | 12 | @previous_iteration = 0 13 | @previous_epoch_detail = 0.0 14 | end 15 | 16 | # Decides whether the extension should be called on this iteration. 17 | # 18 | # @param [Chainer::Trainer] trainer Trainer object that this trigger is associated with. 19 | # The updater associated with this trainer is used to determine if the trigger should fire. 20 | # @return [boolean] True if the corresponding extension should be invoked in this iteration. 21 | def call(trainer) 22 | updater = trainer.updater 23 | if @unit == 'epoch' 24 | epoch_detail = updater.epoch_detail 25 | previous_epoch_detail = @previous_epoch_detail 26 | 27 | if previous_epoch_detail < 0 28 | previous_epoch_detail = updater.previous_epoch_detail 29 | end 30 | 31 | @count = epoch_detail.div(@period).floor 32 | 33 | fire = previous_epoch_detail.div(@period).floor != epoch_detail.div(@period).floor 34 | else 35 | iteration = updater.iteration 36 | previous_iteration = @previous_iteration 37 | if previous_iteration < 0 38 | previous_iteration = iteration - 1 39 | end 40 | fire = previous_iteration.div(@period).floor != iteration.div(@period).floor 41 | end 42 | 43 | # save current values 44 | @previous_iteration = updater.iteration 45 | @previous_epoch_detail = updater.epoch_detail 46 | 47 | fire 48 | end 49 | 50 | def serialize(serializer) 51 | @previous_iteration = serializer.('previous_iteration', @previous_iteration) 52 | @previous_epoch_detail = serializer.('previous_epoch_detail', @previous_epoch_detail) 53 | end 54 | end 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /lib/chainer/training/updater.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Training 3 | class Updater 4 | def connect_trainer(trainer) 5 | end 6 | 7 | def finalize 8 | end 9 | 10 | def get_optimizer(name) 11 | raise NotImplementedError 12 | end 13 | 14 | def get_all_optimizers 15 | raise NotImplementedError 16 | end 17 | 18 | def update 19 | raise NotImplementedError 20 | end 21 | 22 | def serialize(serializer) 23 | raise NotImplementedError 24 | end 25 | 26 | # this method uses in ERB 27 | # example: ERB.new("<%= self %>").result(updater.bind) 28 | def bind 29 | binding 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/chainer/training/util.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Training 3 | module Util 4 | def self.get_trigger(trigger) 5 | if trigger.nil? 6 | false 7 | else 8 | Triggers::IntervalTrigger.new(*trigger) 9 | end 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/chainer/utils/array.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Utils 3 | module Array 4 | def self.force_array(x, dtype=nil) 5 | if x.is_a? Integer or x.is_a? Float 6 | if dtype.nil? 7 | xm = Chainer::Device.default.xm 8 | xm::NArray.cast(x) 9 | else 10 | dtype.cast(x.dup) 11 | end 12 | else 13 | if dtype.nil? 14 | x 15 | else 16 | dtype.cast(x) 17 | end 18 | end 19 | end 20 | 21 | def self.ndindex(shape) 22 | shape.reduce(&:*).times.map do |i| 23 | shape.size.times.reduce([]) do |ndidx, j| 24 | ndidx << (i / shape.drop(j + 1).reduce(1, &:*)) % shape[j] 25 | end 26 | end 27 | end 28 | 29 | def self.take(x, indices, axis: nil) 30 | xm = Chainer.get_array_module(x) 31 | if axis 32 | dimensional_indices = ::Array.new(x.shape.size, true) 33 | 34 | indices_narray = xm::Int32.cast(indices) 35 | if indices_narray.shape.size > 1 36 | y = x.class.zeros(*indices_narray.shape, *x.shape.drop(axis + 1)) 37 | self.ndindex(indices_narray.shape).each do |ndidx| 38 | dimensional_indices[axis] = indices_narray[*ndidx].to_i 39 | y[*ndidx, *::Array.new(x.shape.size - axis - 1, true)] = x[*dimensional_indices] 40 | end 41 | return y 42 | else 43 | dimensional_indices[axis] = indices 44 | end 45 | x[*dimensional_indices] 46 | else 47 | x[indices] 48 | end 49 | end 50 | 51 | def self.rollaxis(y, axis, start: 0) 52 | n = y.ndim 53 | # normalize axis 54 | axis = axis < 0 ? n + axis : axis 55 | if axis >= n 56 | raise ArgumentError, "axis #{axis} is out of bounds for array of dimension #{n}" 57 | end 58 | 59 | if start < 0 60 | start += n 61 | end 62 | 63 | unless 0 <= start && start < n + 1 64 | raise ArgumentError, "start arg requires #{-n} <= start < #{n}, but #{start} was passed in" 65 | end 66 | 67 | if axis < start 68 | start -= 1 69 | end 70 | 71 | if axis == start 72 | return y 73 | end 74 | 75 | axes = (0...n).to_a 76 | axes.delete_at(axis) 77 | axes.insert(start <= axes.size ? start : -1, axis) 78 | y.transpose(*axes) 79 | end 80 | 81 | def self.broadcast_to(x, shape) 82 | if x.shape.size > shape.size 83 | raise TypeError, "Shape of data mismatch\n x.shape.size(#{x.shape.size}) > shape.size(#{shape.size})" 84 | end 85 | 86 | tile_shape = [] 87 | if x.shape.size > 0 88 | shape[-x.shape.size..-1].each_with_index do |s, i| 89 | if x.shape[i] == 1 90 | tile_shape << s 91 | elsif x.shape[i] == s 92 | tile_shape << 1 93 | else 94 | raise TypeError, "Shape of data mismatch\n#{x.shape} != #{shape}" 95 | end 96 | end 97 | else 98 | tile_shape = shape 99 | end 100 | 101 | x.tile(*shape[0...-x.shape.size], *tile_shape) 102 | end 103 | end 104 | end 105 | end 106 | -------------------------------------------------------------------------------- /lib/chainer/utils/conv.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Utils 3 | module Conv 4 | def self.get_conv_outsize(size, k, s, p, cover_all: false, d: 1) 5 | dk = k + (k - 1) * (d - 1) 6 | if cover_all 7 | (size + p * 2 - dk + s - 1).div(s) + 1 8 | else 9 | (size + p * 2 - dk).div(s) + 1 10 | end 11 | end 12 | 13 | def self.get_deconv_outsize(size, k, s, p, cover_all: false) 14 | if cover_all 15 | s * (size - 1) + k -s + 1 - 2 * p 16 | else 17 | s * (size - 1) + k - 2 * p 18 | end 19 | end 20 | 21 | def self.im2col(img, kh, kw, sy, sx, ph, pw, pval: 0, cover_all: false, dy: 1, dx: 1) 22 | n, c, h, w = img.shape 23 | 24 | out_h = self.get_conv_outsize(h, kh, sy, ph, cover_all: cover_all, d: dy) 25 | raise 'Height in the output should be positive.' if out_h <= 0 26 | out_w = self.get_conv_outsize(w, kw, sx, pw, cover_all: cover_all, d: dx) 27 | raise 'Width in the output should be positive.' if out_w <= 0 28 | 29 | # padding 30 | # TODO: ref: numpy.pad 31 | pad_bottom = ph + sy - 1 32 | pad_right = pw + sx - 1 33 | pad_img = img.class.new(n, c, (h + ph + pad_bottom), (w + pw + pad_right)).fill(pval) 34 | pad_img[nil, nil, ph...(ph+h), pw...(pw+w)] = img 35 | 36 | col = pad_img.class.new(n, c, kh, kw, out_h, out_w).rand(1) 37 | 38 | kh.times do |j| 39 | jdy = j * dy 40 | j_lim = [jdy + sy * out_h, pad_img.shape[2]].min 41 | kw.times do |i| 42 | idx = i * dx 43 | i_lim = [idx + sx * out_w, pad_img.shape[3]].min 44 | col[nil, nil, j, i, nil, nil] = pad_img[nil, nil, (jdy...j_lim).step(sy), (idx...i_lim).step(sx)] 45 | end 46 | end 47 | 48 | col 49 | end 50 | 51 | def self.col2im(col, sy, sx, ph, pw, h, w, dy: 1, dx: 1) 52 | n, c, kh, kw, out_h, out_w = col.shape 53 | img = col.class.zeros(n, c, h + 2 * ph + sy - 1, w + 2 * pw + sx - 1) 54 | kh.times do |j| 55 | jdy = j * dy 56 | j_lim = [jdy + sy * out_h, img.shape[2]].min 57 | kw.times do |i| 58 | idx = i * dx 59 | i_lim = [idx + sx * out_w, img.shape[3]].min 60 | img[nil, nil, (jdy...j_lim).step(sy), (idx...i_lim).step(sx)] += col[nil, nil, j, i, nil, nil] 61 | end 62 | end 63 | return img[nil, nil, (ph...(h + ph)), (pw...(w + pw))] 64 | end 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /lib/chainer/utils/initializer.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Utils 3 | module Initializer 4 | def self.get_fans(shape, device: Chainer::Device.default) 5 | raise 'shape must be of length >= 2: shape={}' if shape.size < 2 6 | slice_arr = shape.slice(2, shape.size) 7 | receptive_field_size = slice_arr.empty? ? 1 : device.xm::Int32[slice_arr].prod 8 | fan_in = shape[1] * receptive_field_size 9 | fan_out = shape[0] * receptive_field_size 10 | [fan_in, fan_out] 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/chainer/utils/math.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Utils 3 | module Math 4 | def self.tensordot(a, b, axes) 5 | if axes.is_a?(Integer) 6 | axes_a = (-axes...0).to_a 7 | axes_b = (0...axes).to_a 8 | else axes.is_a?(Array) 9 | axes_a, axes_b = axes 10 | end 11 | 12 | axes_a = Array(axes_a) 13 | axes_b = Array(axes_b) 14 | na = axes_a.size 15 | nb = axes_b.size 16 | 17 | as = a.shape 18 | nda = a.ndim 19 | bs = b.shape 20 | ndb = b.ndim 21 | equal = true 22 | if na != nb 23 | equal = false 24 | else 25 | na.times do |k| 26 | if as[axes_a[k]] != bs[axes_b[k]] 27 | equal = false 28 | break 29 | end 30 | 31 | if axes_a[k] < 0 32 | axes_a[k] += nda 33 | end 34 | 35 | if axes_b[k] < 0 36 | axes_b[k] += ndb 37 | end 38 | end 39 | end 40 | 41 | raise "shape-mismatch for sum" unless equal 42 | 43 | notin = (0...nda).reject { |i| axes_a.include?(i) } 44 | newaxes_a = notin + axes_a 45 | n2 = 1 46 | axes_a.each do |axis| 47 | n2 *= as[axis] 48 | end 49 | tmp = a.shape.reduce(:*) / n2 50 | newshape_a = [tmp, n2] 51 | olda = notin.map { |axis| as[axis] } 52 | 53 | notin = (0...ndb).reject { |i| axes_b.include?(i) } 54 | newaxes_b = axes_b + notin 55 | n2 = 1 56 | axes_b.each do |axis| 57 | n2 *= bs[axis] 58 | end 59 | tmp = b.shape.reduce(:*) / n2 60 | newshape_b = [n2, tmp] 61 | oldb = notin.map { |axis| bs[axis] } 62 | 63 | at = a.transpose(*newaxes_a).reshape(*newshape_a) 64 | bt = b.transpose(*newaxes_b).reshape(*newshape_b) 65 | res = at.dot(bt) 66 | 67 | return res.reshape(*(olda + oldb)) 68 | end 69 | end 70 | end 71 | end 72 | 73 | -------------------------------------------------------------------------------- /lib/chainer/utils/variable.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | module Utils 3 | module Variable 4 | def self.check_grad_type(func, x, gx) 5 | if x.data.nil? || gx.nil? 6 | return 7 | end 8 | 9 | unless gx.is_a?(x.data.class.superclass) 10 | raise TypeError, "Type of data and grad mismatch\n#{x.data.class} != #{gx.class}" 11 | end 12 | 13 | unless gx.class == x.data.class 14 | raise TypeError, "Dtype(Class) of data and grad mismatch\n#{x.data.class} != #{gx.class}" 15 | end 16 | 17 | unless gx.shape == x.data.shape 18 | raise TypeError, "Shape of data and grad mismatch\n#{x.data.shape} != #{gx.shape}" 19 | end 20 | end 21 | end 22 | end 23 | end 24 | 25 | -------------------------------------------------------------------------------- /lib/chainer/variable_node.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | class VariableNode 3 | attr_reader :dtype, :shape, :data 4 | attr_accessor :name, :requires_grad, :variable, :creator_node, :rank, :old_style_grad_generator 5 | 6 | def initialize(variable: , name:) 7 | @variable = WeakRef.new(variable) 8 | @creator_node = nil 9 | @data = nil 10 | @rank = 0 11 | @name = name 12 | @requires_grad = variable.requires_grad 13 | 14 | @old_style_grad_generator = nil 15 | 16 | set_data_type(variable.data) 17 | end 18 | 19 | def creator 20 | node = @creator_node 21 | if node.nil? 22 | return nil 23 | end 24 | 25 | if node.is_a?(Chainer::FunctionAdapter) 26 | return node.function 27 | end 28 | node 29 | end 30 | 31 | def creator=(func) 32 | self.creator_node = func 33 | end 34 | 35 | def creator_node=(func) 36 | func = func.node if func.is_a?(Chainer::Function) 37 | @creator_node = func 38 | unless func.nil? 39 | @rank = func.rank + 1 40 | end 41 | end 42 | 43 | def data=(data) 44 | @data = data 45 | set_data_type(data) 46 | end 47 | 48 | # Gradient array of the corresponding variable. 49 | def grad 50 | var = get_variable 51 | var.nil? ? nil : var.grad 52 | end 53 | 54 | # Gradient variable of the corresponding variable. 55 | def grad_var 56 | var = get_variable 57 | var.nil? ? nil : var.grad_var 58 | end 59 | 60 | def label 61 | if @shape.nil? || @shape.empty? 62 | @dtype.to_s 63 | else 64 | "(#{@shape.join(', ')}), #{@dtype.to_s}" 65 | end 66 | end 67 | 68 | # Returns the corresponding :class:`Variable` object. 69 | # 70 | # @return [Chainer::Variable] The variable object that refers this node. 71 | def get_variable 72 | var = @variable 73 | # workaround: check weakref_alive?, because weakref sometimes delegates references by GC 74 | return var.__getobj__ if !var.nil? && var.weakref_alive? 75 | 76 | var = Chainer::Variable.new(@data, name: @name, requires_grad: @requires_grad) 77 | var.node = self 78 | var 79 | end 80 | 81 | def set_creator(creator) 82 | self.creator = creator 83 | end 84 | 85 | # Sets a `FunctionNode` object that created this node. 86 | # 87 | # @param [Chainer::FunctionNode] creator_node Function node that has this variable as an output. 88 | def set_creator_node(creator_node) 89 | self.creator_node = creator_node 90 | end 91 | 92 | def unchain 93 | self.creator_node = nil 94 | end 95 | 96 | def retain_data 97 | if @variable.nil? 98 | raise "cannot retain variable data: the variable has been already released" 99 | else 100 | @variable.data 101 | end 102 | end 103 | 104 | def set_data_type(data) 105 | if data.nil? 106 | @dtype = nil 107 | @shape = nil 108 | else 109 | @dtype = data.class 110 | @shape = data.shape 111 | end 112 | end 113 | 114 | def set_grad_with_check(g, func, var) 115 | Utils::Variable.check_grad_type(func, var, g) 116 | @grad = g 117 | end 118 | 119 | def check_old_style_gradient 120 | if @old_style_grad_generator 121 | raise RuntimeError, "cannot twice-differentiate an old style Function #{@old_style_grad_generator}" 122 | end 123 | end 124 | end 125 | end 126 | -------------------------------------------------------------------------------- /lib/chainer/version.rb: -------------------------------------------------------------------------------- 1 | module Chainer 2 | VERSION = "0.4.1" 3 | end 4 | 5 | -------------------------------------------------------------------------------- /red-chainer.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path("../lib", __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require "chainer/version" 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "red-chainer" 8 | spec.version = Chainer::VERSION 9 | spec.authors = ["Yusaku Hatanaka"] 10 | spec.email = ["hatappi@hatappi.me"] 11 | 12 | spec.summary, spec.description = "A flexible framework for neural network for Ruby" 13 | spec.homepage = "https://github.com/red-data-tools/red-chainer" 14 | spec.license = "MIT" 15 | spec.files = `git ls-files -z`.split("\x0").reject do |f| 16 | f.match(%r{^(test|spec|features)/}) 17 | end 18 | spec.bindir = "exe" 19 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 20 | spec.require_paths = ["lib"] 21 | 22 | spec.add_runtime_dependency "numo-narray", ">= 0.9.1.1" 23 | spec.add_runtime_dependency "red-datasets", ">= 0.0.6" 24 | 25 | spec.add_development_dependency "bundler" 26 | spec.add_development_dependency "rake", "~> 13.0" 27 | spec.add_development_dependency "test-unit", ">= 3.2.9" 28 | spec.add_development_dependency "yard", ">= 0.9.10" 29 | end 30 | -------------------------------------------------------------------------------- /templates/default/layout/html/layout.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= erb(:headers) %> 5 | 6 | 7 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 27 | 28 |
29 | 34 | 35 |
<%= yieldall %>
36 | 37 | <%= erb(:footer) %> 38 |
39 | 40 | 41 | -------------------------------------------------------------------------------- /templates/default/onefile/html/layout.erb: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | <%= defined?(@title) ? @title : '' %> 7 | <%= erb(:headers) %> 8 | 9 | 10 | 18 | 21 | 22 | 23 | 24 | 25 | 26 |
27 |

<%= defined?(@title) ? @title : '' %>

28 | <%= yieldall %> 29 |
30 | 31 | <%= erb(:footer) %> 32 | 33 | 34 | -------------------------------------------------------------------------------- /test/backend_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'chainer' 4 | 5 | class TestBackend < Test::Unit::TestCase 6 | def test_get_array_module 7 | assert xm == Chainer.get_array_module(xm::NArray[]) 8 | assert xm == Chainer.get_array_module(Chainer::Variable.new(xm::NArray[])) 9 | end 10 | 11 | def test_array_p 12 | assert_equal(xm, Chainer.get_array_module(xm::NArray[])) 13 | assert_equal(xm, Chainer.get_array_module(Chainer::Variable.new(xm::NArray[]))) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /test/device_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'chainer' 4 | 5 | class TestDevice < Test::Unit::TestCase 6 | class TestCpuDevice < Test::Unit::TestCase 7 | def test_xm 8 | assert Chainer::CpuDevice.new.xm == Numo 9 | end 10 | 11 | def test_eq 12 | assert Chainer::CpuDevice.new == Chainer::CpuDevice.new 13 | end 14 | 15 | def test_use 16 | Chainer::CpuDevice.new.use # nothing raised 17 | end 18 | end 19 | 20 | class TestGpuDevice < Test::Unit::TestCase 21 | def setup 22 | require_gpu 23 | end 24 | 25 | def test_xm 26 | assert Chainer::GpuDevice.new.xm == Cumo 27 | end 28 | 29 | def test_id 30 | assert Chainer::GpuDevice.new(0).id == 0 31 | end 32 | 33 | def test_eq 34 | assert Chainer::GpuDevice.new == Chainer::GpuDevice.new(0) 35 | end 36 | 37 | def test_use 38 | orig_device_id = Cumo::CUDA::Runtime.cudaGetDevice 39 | begin 40 | Chainer::GpuDevice.new(0).use 41 | assert Cumo::CUDA::Runtime.cudaGetDevice == 0 42 | ensure 43 | Cumo::CUDA::Runtime.cudaSetDevice(orig_device_id) 44 | end 45 | end 46 | 47 | def test_use_1 48 | require_gpu(1) 49 | orig_device_id = Cumo::CUDA::Runtime.cudaGetDevice 50 | begin 51 | Chainer::GpuDevice.new(1).use 52 | assert Cumo::CUDA::Runtime.cudaGetDevice == 1 53 | ensure 54 | Cumo::CUDA::Runtime.cudaSetDevice(orig_device_id) 55 | end 56 | end 57 | end 58 | 59 | class TestCreateDevice < Test::Unit::TestCase 60 | def test_device 61 | device = Chainer::CpuDevice.new 62 | assert Chainer::Device.create(device) === device 63 | end 64 | 65 | def test_negative_integer 66 | assert Chainer::Device.create(-1) == Chainer::CpuDevice.new 67 | end 68 | 69 | def test_non_negative_integer 70 | require_gpu 71 | assert Chainer::Device.create(0) == Chainer::GpuDevice.new(0) 72 | end 73 | end 74 | 75 | def test_change_get_default 76 | orig_default = Chainer::Device.default 77 | begin 78 | device = Chainer::CpuDevice.new 79 | Chainer::Device.change_default(device) 80 | # Chainer::Device.set_default(device) 81 | # Chainer::Device.default = device 82 | assert Chainer::Device.default === device 83 | ensure 84 | Chainer::Device.change_default(orig_default) 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /test/function_node_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'chainer' 4 | 5 | class TestGradTypeCheck < Test::Unit::TestCase 6 | def test_type_check 7 | x = Chainer::Variable.new(xm::DFloat.new(2, 3).rand(-1, 1)) 8 | y = x * x 9 | gx = Chainer::Variable.new(xm::DFloat.new(2, 3).rand(-1, 1)) 10 | gy = Chainer::Variable.new(xm::DFloat.new(2, 3).rand(-1, 1)) 11 | 12 | Chainer.grad([y], [x], grad_outputs: [gx], grad_inputs: [gy]) 13 | 14 | assert_raise(TypeError) do 15 | Chainer.grad(y, [x], grad_outputs: [gx], grad_inputs: [gy]) 16 | end 17 | assert_raise(TypeError) do 18 | Chainer.grad([y], x, grad_outputs: [gx], grad_inputs: [gy]) 19 | end 20 | assert_raise(TypeError) do 21 | Chainer.grad([y], [x], grad_outputs: gx, grad_inputs: [gy]) 22 | end 23 | assert_raise(TypeError) do 24 | Chainer.grad([y], [x], grad_outputs: [gx], grad_inputs: gy) 25 | end 26 | end 27 | end 28 | 29 | class Chainer::GradTestBase < Test::Unit::TestCase 30 | def setup 31 | @shape ||= [3] 32 | @x_names ||= [] 33 | @y_names ||= [] 34 | 35 | @xs = init_attrs(@x_names) 36 | @gxs = init_attrs(to_grad_names(@x_names)) 37 | @gys = init_attrs(to_grad_names(@y_names)) 38 | 39 | end 40 | 41 | def forward 42 | raise NotImplementedError 43 | end 44 | 45 | def expected_grad 46 | raise NotImplementedError 47 | end 48 | 49 | def expected_double_grad 50 | raise NotImplementedError 51 | end 52 | 53 | def check_grad 54 | forward 55 | ys = @y_names.map { |name| self.instance_variable_get("@#{name}") } 56 | gxs = Chainer.grad(ys, @xs, grad_outputs: @gys, grad_inputs: @gxs) 57 | 58 | expected = expected_grad 59 | @gxs.each_with_index do |gx, i| 60 | expected[i] += gx 61 | end 62 | 63 | assert_equal(expected.size, gxs.size) 64 | gxs.zip(expected).each do |a, e| 65 | Chainer::Testing.assert_allclose(get_value(e), get_value(a)) 66 | end 67 | end 68 | 69 | def check_double_grad 70 | forward 71 | ys = @y_names.map { |name| self.instance_variable_get("@#{name}") } 72 | gxs = Chainer.grad(ys, @xs, grad_outputs: @gys, grad_inputs: @gxs, enable_double_backprop: true) 73 | y = gxs.sum 74 | ggxs = Chainer.grad([y], @xs) 75 | 76 | expected = expected_double_grad 77 | assert_equal(expected.size, ggxs.size) 78 | ggxs.zip(expected).each do |a, e| 79 | Chainer::Testing.assert_allclose(get_value(e), get_value(a)) 80 | end 81 | end 82 | 83 | private 84 | 85 | def to_grad_names(names) 86 | names.map { |n| "g#{n}" } 87 | end 88 | 89 | def init_attrs(names) 90 | ret = [] 91 | names.each do |name| 92 | x = xm::DFloat.new(@shape).rand(-4, 6) 93 | v = Chainer::Variable.new(x, name: name) 94 | ret << v 95 | self.instance_variable_set("@#{name}", v) 96 | end 97 | ret 98 | end 99 | 100 | def get_value(x) 101 | x.is_a?(Chainer::Variable) ? x.data : x 102 | end 103 | end 104 | 105 | class Chainer::TestGradSimple < Chainer::GradTestBase 106 | def setup 107 | @x_names = ['x'] 108 | @y_names = ['y'] 109 | super 110 | end 111 | 112 | def forward 113 | @y = @x * @x 114 | end 115 | 116 | def expected_grad 117 | [2 * @x * @gy] 118 | end 119 | 120 | def expected_double_grad 121 | [2 * @gy] 122 | end 123 | 124 | def test_grad 125 | check_grad 126 | end 127 | 128 | def test_double_grad 129 | check_double_grad 130 | end 131 | end 132 | 133 | class TestGradComplex < Chainer::GradTestBase 134 | def setup 135 | @x_names = ['x1', 'x2'] 136 | @y_names = ['y1', 'y2'] 137 | super 138 | end 139 | 140 | def forward 141 | @z = @x1 * @x1 142 | @y1 = @z + @x1 * @x2 + @x2 143 | @y2 = @z + @y1 144 | end 145 | 146 | def expected_grad 147 | dz_dx = 2 * @x1 148 | dy1_dx = @gy1 + @gy2 149 | [dy1_dx * (dz_dx + @x2) + @gy2 * dz_dx, dy1_dx * (@x1 + 1)] 150 | end 151 | 152 | def expected_double_grad 153 | dy1_dx = @gy1 + @gy2 154 | [3 * dy1_dx + 2 * @gy2, dy1_dx] 155 | end 156 | 157 | def test_grad 158 | check_grad 159 | end 160 | 161 | def test_double_grad 162 | check_double_grad 163 | end 164 | end 165 | -------------------------------------------------------------------------------- /test/functions/activation/leaky_relu_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'chainer/functions/activation/leaky_relu' 4 | 5 | class Chainer::Functions::Activation::LeakyReLUTest < Test::Unit::TestCase 6 | 7 | data(:shape, [[3, 2], []], keep: true) 8 | data(:dtype, [xm::SFloat, xm::DFloat], keep: true) 9 | 10 | def setup 11 | # Avoid unstability of numerical grad 12 | @shape = data[:shape] 13 | @dtype = data[:dtype] 14 | 15 | @dtype.srand(1) # To avoid false of "nearly_eq.all?", Use fixed seed value. 16 | @x = @dtype.new(@shape).rand(2) - 1 17 | @shape.map do |x| 18 | if (-0.05 < x) and (x < 0.05) 19 | 0.5 20 | else 21 | x 22 | end 23 | end 24 | @gy = @dtype.new(@shape).rand(2) - 1 25 | @ggx = @dtype.new(@shape).rand(2) - 1 26 | @slope = Random.rand 27 | @check_forward_options = {} 28 | @check_backward_options = {} 29 | @check_backward_options_dtype = xm::DFloat 30 | end 31 | 32 | def check_forward(x_data) 33 | x = Chainer::Variable.new(x_data) 34 | y = Chainer::Functions::Activation::LeakyReLU.leaky_relu(x, slope: @slope) 35 | assert_equal(@dtype, y.data.class) 36 | expected = @x.dup 37 | if expected.shape == [] 38 | expected[expected < 0] *= @slope 39 | else 40 | @x.each_with_index do |x, *i| 41 | if x < 0 42 | expected[*i] *= @slope 43 | end 44 | end 45 | end 46 | assert_true(y.data.nearly_eq(expected).all?) 47 | end 48 | 49 | def test_forward 50 | check_forward(@x.dup) 51 | end 52 | 53 | def check_backward(x_data, y_grad) 54 | func = -> (x) do 55 | Chainer::Functions::Activation::LeakyReLU.leaky_relu(x, slope: @slope) 56 | end 57 | Chainer::check_backward(func, x_data, y_grad, dtype: @check_backward_options_dtype) 58 | end 59 | 60 | def test_backward 61 | check_backward(@x.dup, @gy.dup) 62 | end 63 | 64 | def check_double_backward(x_data, y_grad, x_grad_grad) 65 | func = -> (x) do 66 | y = Chainer::Functions::Activation::LeakyReLU.leaky_relu(x, slope: @slope) 67 | y * y 68 | end 69 | 70 | Chainer::check_double_backward(func, x_data, y_grad, x_grad_grad, **@check_backward_options) 71 | end 72 | 73 | def test_double_backward 74 | check_double_backward(@x, @gy, @ggx) 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /test/functions/activation/log_softmax_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'numo/narray' 4 | require 'chainer' 5 | require 'chainer/functions/activation/log_softmax' 6 | 7 | class Chainer::Functions::Activation::LogSoftmaxTest < Test::Unit::TestCase 8 | 9 | data(:shape, [nil, [2, 3], [2, 2, 3], [2, 2, 2, 3]], keep: true) 10 | data(:dtype, [xm::SFloat, xm::DFloat], keep: true) 11 | 12 | def setup 13 | @shape = data[:shape] 14 | @dtype = data[:dtype] 15 | if @shape.nil? 16 | value = -1000 17 | @x = @dtype.cast([[value, 1]]) 18 | else 19 | @dtype.srand(1) # To avoid false of "assert_allclose", Use fixed seed value. 20 | @x = @dtype.new(@shape).rand(2) - 1 21 | end 22 | @gy = @dtype.new(@x.shape).rand(2) - 1 23 | @ggx = @dtype.new(@x.shape).rand(2) - 1 24 | @check_forward_options = {} 25 | @check_backward_options = {dtype: xm::DFloat} 26 | end 27 | 28 | def check_forward(x_data, use_cudnn: "always") 29 | x = Chainer::Variable.new(x_data) 30 | y = Chainer::Functions::Activation::LogSoftmax.log_softmax(x).dup 31 | assert_equal(@dtype, y.data.class) 32 | 33 | xm = Chainer.get_array_module(@x) 34 | log_z = xm::NMath.log(xm::NMath.exp(@x).sum(axis:1, keepdims:true)) 35 | y_expect = @x - log_z 36 | Chainer::Testing.assert_allclose(y.data, y_expect) 37 | end 38 | 39 | def test_forward 40 | check_forward(@x.dup) 41 | end 42 | 43 | def check_backward(x_data, gy_data, use_cudnn: "always") 44 | Chainer::check_backward(Chainer::Functions::Activation::LogSoftmax.method(:log_softmax), x_data, gy_data, **@check_backward_options) 45 | end 46 | 47 | def test_backward 48 | check_backward(@x.dup, @gy.dup) 49 | end 50 | 51 | def check_double_backward(x_data, gy_data, ggx_data, use_cudnn: 'always') 52 | Chainer::check_double_backward(Chainer::Functions::Activation::LogSoftmax.method(:log_softmax), x_data, gy_data, ggx_data, **@check_backward_options) 53 | end 54 | 55 | def test_double_backward 56 | check_double_backward(@x, @gy, @ggx) 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /test/functions/activation/relu_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'chainer/functions/activation/relu' 4 | 5 | class Chainer::Functions::Activation::ReLUTest < Test::Unit::TestCase 6 | 7 | data(:shape, [[3, 2], []], keep: true) 8 | data(:dtype, [xm::SFloat, xm::DFloat], keep: true) 9 | 10 | def setup 11 | # Avoid unstability of numerical grad 12 | @shape = data[:shape] 13 | @dtype = data[:dtype] 14 | 15 | @x = @dtype.new(@shape).rand(-1, 1) 16 | @shape.map do |x| 17 | if (-0.1 < x) and (x < 0.1) 18 | 0.5 19 | else 20 | x 21 | end 22 | end 23 | 24 | @gy = @dtype.new(@shape).rand(-1, 1) 25 | @ggx = @dtype.new(@shape).rand(-1, 1) 26 | @check_backward_options = {} 27 | end 28 | 29 | def check_forward(x_data, use_cudnn: "always") 30 | x = Chainer::Variable.new(x_data) 31 | y = Chainer::Functions::Activation::Relu.relu(x) 32 | assert_equal(@dtype, y.data.class) 33 | expected = @x.dup 34 | if expected.shape == [] 35 | expected[expected < 0] = 0 36 | else 37 | @x.each_with_index do |x, *i| 38 | if x < 0 39 | expected[*i] = 0 40 | end 41 | end 42 | end 43 | assert_true(y.data.nearly_eq(expected).all?) 44 | end 45 | 46 | def test_forward 47 | check_forward(@x.dup) 48 | end 49 | 50 | def check_backward(x_data, y_grad, use_cudnn: "always") 51 | Chainer::check_backward(Chainer::Functions::Activation::Relu.method(:relu), x_data, y_grad, **@check_backward_options) 52 | end 53 | 54 | def test_backward 55 | check_backward(@x.dup, @gy.dup) 56 | end 57 | 58 | def check_double_backward(x_data, y_grad, x_grad_grad, use_cudnn: 'always') 59 | func = -> (x) do 60 | x = Chainer::Functions::Activation::Relu.relu(x) 61 | x * x 62 | end 63 | 64 | Chainer::check_double_backward(func, x_data, y_grad, x_grad_grad, **@check_backward_options) 65 | end 66 | 67 | def test_double_backward 68 | check_double_backward(@x, @gy, @ggx) 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /test/functions/activation/sigmoid_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'chainer/functions/activation/sigmoid' 4 | 5 | class Chainer::Functions::Activation::SigmoidTest < Test::Unit::TestCase 6 | 7 | data(:shape, [[3, 2], []], keep: true) 8 | data(:dtype, [xm::SFloat, xm::DFloat], keep: true) 9 | 10 | def setup 11 | @shape = data[:shape] 12 | @dtype = data[:dtype] 13 | @x = @dtype.new(@shape).rand(-0.5, 0.5) 14 | @gy = @dtype.new(@shape).rand(-0.1, 0.1) 15 | @ggx = @dtype.new(@shape).rand(-1, 1) 16 | @check_forward_options = {} 17 | @check_backward_options = {} 18 | @check_double_backward_options = {} 19 | end 20 | 21 | def check_forward(x_data, use_cudnn: "always") 22 | x = Chainer::Variable.new(x_data) 23 | y = Chainer::Functions::Activation::Sigmoid.sigmoid(x) 24 | assert_equal(@dtype, y.data.class) 25 | y_expect = Chainer::Functions::Activation::Sigmoid.sigmoid(Chainer::Variable.new(@x)) 26 | Chainer::Testing.assert_allclose(y_expect.data, y.data, **@check_forward_options) 27 | end 28 | 29 | def test_forward 30 | check_forward(@x.dup) 31 | end 32 | 33 | def check_backward(x_data, y_grad, use_cudnn: "always") 34 | Chainer::check_backward(Chainer::Functions::Activation::Sigmoid.method(:sigmoid), x_data, y_grad, **@check_backward_options) 35 | end 36 | 37 | def test_backward 38 | check_backward(@x.dup, @gy.dup) 39 | end 40 | 41 | def check_double_backward(x_data, y_grad, x_grad_grad, use_cudnn: 'always') 42 | func = -> (x) do 43 | y = Chainer::Functions::Activation::Sigmoid.sigmoid(x) 44 | y * y 45 | end 46 | Chainer::check_double_backward(func, x_data, y_grad, x_grad_grad, **@check_backward_options) 47 | end 48 | 49 | def test_double_backward 50 | check_double_backward(@x, @gy, @ggx) 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /test/functions/activation/tanh_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'chainer/functions/activation/tanh' 4 | 5 | class Chainer::Functions::Activation::TanhTest < Test::Unit::TestCase 6 | data(:shape, [[3, 2], []], keep: true) 7 | data(:dtype, [xm::SFloat, xm::DFloat], keep: true) 8 | 9 | def setup 10 | @shape = data[:shape] 11 | @dtype = data[:dtype] 12 | @dtype.srand(1) # To avoid false of "nearly_eq.all?", Use fixed seed value. 13 | 14 | @x = @dtype.new(@shape).rand(-0.5, 0.5) 15 | @gy = @dtype.new(@shape).rand(-0.5, 0.5) 16 | @ggx = @dtype.new(@shape).rand(-1, 1) 17 | @check_backward_options = {} 18 | end 19 | 20 | def check_forward(x_data, use_cudnn: "always") 21 | x = Chainer::Variable.new(x_data) 22 | y = Chainer::Functions::Activation::Tanh.tanh(x) 23 | assert_equal(@dtype, y.data.class) 24 | y_expect = Chainer::Functions::Activation::Tanh.tanh(Chainer::Variable.new(@x)) 25 | assert_true(y.data.nearly_eq(y_expect.data).all?) 26 | end 27 | 28 | def test_forward 29 | check_forward(@x.dup) 30 | end 31 | 32 | def check_backward(x_data, gy_data, use_cudnn: "always") 33 | Chainer::check_backward(Chainer::Functions::Activation::Tanh.method(:tanh), x_data, gy_data, **@check_backward_options) 34 | end 35 | 36 | def test_backward 37 | check_backward(@x.dup, @gy.dup) 38 | end 39 | 40 | def check_double_backward(x_data, y_grad, x_grad_grad, use_cudnn: 'always') 41 | func = -> (x) do 42 | y = Chainer::Functions::Activation::Tanh.tanh(x) 43 | y * y 44 | end 45 | Chainer::check_double_backward(func, x_data, y_grad, x_grad_grad, **@check_backward_options) 46 | end 47 | 48 | def test_double_backward 49 | check_double_backward(@x, @gy, @ggx) 50 | end 51 | end 52 | 53 | class Chainer::Functions::Activation::TanhGradTest < Test::Unit::TestCase 54 | data(:shape, [[3, 2], []], keep: true) 55 | data(:dtype, [xm::SFloat, xm::DFloat], keep: true) 56 | 57 | def setup 58 | @shape = data[:shape] 59 | @dtype = data[:dtype] 60 | @x = @dtype.new(@shape).rand(-0.5, 0.5) 61 | @gy = @dtype.new(@shape).rand(-1, 1) 62 | @ggx = @dtype.new(@shape).rand(-1, 1) 63 | end 64 | 65 | def check_backward(x_data, y_data, gy_data, ggx_data, use_cudnn: 'always') 66 | func = -> (y, gy) do 67 | Chainer::Functions::Activation::TanhGrad.new(x_data).apply([y, gy]).first 68 | end 69 | Chainer::check_backward(func, [y_data, gy_data], ggx_data, dtype: xm::DFloat, atol: 1e-4, rtol: 1e-4) 70 | end 71 | 72 | def test_backward 73 | y = xm::NMath.tanh(@x) 74 | check_backward(@x, y, @gy, @ggx) 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /test/functions/array/broadcast_to_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'chainer/functions/array/broadcast_to' 4 | 5 | class Chainer::Functions::Array::BroadcastToTest < Test::Unit::TestCase 6 | data(:shape, [ 7 | { in_shape: [3, 1, 5], out_shape: [3, 2, 5] }, 8 | { in_shape: [5,], out_shape: [3, 2, 5] }, 9 | { in_shape: [3, 2, 5], out_shape: [3, 2, 5] } 10 | ], keep: true) 11 | data(:dtype, [ xm::SFloat, xm::DFloat ], keep: true) 12 | 13 | def test_forward(data) 14 | in_data = data[:dtype].new(data[:shape][:in_shape]).rand 15 | x = Chainer::Variable.new(in_data) 16 | bx = Chainer::Functions::Array::BroadcastTo.broadcast_to(x, data[:shape][:out_shape]) 17 | 18 | assert_equal(bx.data.shape, data[:shape][:out_shape]) 19 | end 20 | 21 | def test_backward(data) 22 | in_data = data[:dtype].new(data[:shape][:in_shape]).rand 23 | grads = data[:dtype].new(data[:shape][:out_shape]).rand 24 | check_backward_options = {} 25 | if data[:dtype] == xm::SFloat 26 | check_backward_options = { eps: 2 ** -5, atol: 1e-3, rtol: 1e-2 } 27 | end 28 | 29 | func = lambda{ |x| Chainer::Functions::Array::BroadcastTo.broadcast_to(x, data[:shape][:out_shape]) } 30 | Chainer::check_backward(func, in_data, grads, **check_backward_options) 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /test/functions/array/cast_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'chainer/functions/array/cast' 4 | 5 | class Chainer::Functions::Array::CastTest < Test::Unit::TestCase 6 | data(:shape, [[3, 4], []], keep: true) 7 | data(:type, [ 8 | { in_type: xm::SFloat, out_type: xm::DFloat }, 9 | { in_type: xm::DFloat, out_type: xm::SFloat }, 10 | ], keep: true) 11 | 12 | def setup 13 | @x = data[:type][:in_type].new(*data[:shape]).seq 14 | @g = data[:type][:out_type].new(*data[:shape]).seq 15 | end 16 | 17 | def test_forward(data) 18 | x = Chainer::Variable.new(@x) 19 | y = Chainer::Functions::Array::Cast.cast(x, data[:type][:out_type]) 20 | assert_equal(y.dtype, data[:type][:out_type]) 21 | assert_equal(y.shape, x.shape) 22 | end 23 | 24 | def test_backward(data) 25 | check_backward_options = { eps: 2.0 ** -2, atol: 1e-3, rtol: 1e-2 } 26 | func = -> x { Chainer::Functions::Array::Cast.cast(x, data[:type][:out_type]) } 27 | 28 | Chainer::check_backward(func, @x, @g, **check_backward_options) 29 | end 30 | end 31 | 32 | class Chainer::Functions::Array::NoCastTest < Test::Unit::TestCase 33 | def setup 34 | @dtype = xm::SFloat 35 | @x = @dtype.new(1).rand 36 | end 37 | 38 | def test_forward_no_cast_array 39 | y = Chainer::Functions::Array::Cast.cast(@x, @dtype) 40 | assert_equal(Chainer::Variable, y.class) 41 | assert_equal(@x, y.data) 42 | end 43 | 44 | def test_forward_no_cast_variable 45 | x = Chainer::Variable.new(@x) 46 | y = Chainer::Functions::Array::Cast.cast(x, @dtype) 47 | assert_equal(x, y) 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /test/functions/array/reshape_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'chainer/functions/array/reshape' 4 | 5 | class Chainer::Functions::Array::ReshapeTest < Test::Unit::TestCase 6 | xm = Chainer::Device.default.xm 7 | 8 | data(:in_shape, [[4, 3, 2]], group: :positive, keep: true) 9 | data(:out_shape, [[2, 2, 6]], group: :positive, keep: true) 10 | data(:dtype, [xm::SFloat, xm::DFloat], group: :positive, keep: true) 11 | 12 | data(:in_shape, [[2, 3, 6]], group: :negative, keep: true) 13 | data(:out_shape, [[2, -1]], group: :negative, keep: true) 14 | data(:dtype, [xm::SFloat, xm::DFloat], group: :negative, keep: true) 15 | 16 | def test_forward 17 | shape = data[:out_shape] 18 | in_data = data[:dtype].new(data[:in_shape]).rand(-1, 1) 19 | x = Chainer::Variable.new(in_data) 20 | y = Chainer::Functions::Array::Reshape.reshape(x, shape) 21 | assert_equal(y.data.class, data[:dtype]) 22 | assert_equal(x.reshape(*shape).data, y.data) 23 | end 24 | 25 | def test_backward 26 | in_data = data[:dtype].new(data[:in_shape]).rand(-1, 1) 27 | x = Chainer::Variable.new(in_data) 28 | y = Chainer::Functions::Array::Reshape.reshape(x, data[:out_shape]) 29 | y.grad = y.data 30 | y.backward 31 | 32 | Chainer::Testing.assert_allclose(x.data, x.grad, atol: 0, rtol: 0) 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /test/functions/array/rollaxis_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'chainer/functions/array/rollaxis' 4 | 5 | class Chainer::Functions::Array::RollaxisTest < Test::Unit::TestCase 6 | data(:test_case, [ 7 | {axis: 0, start: 2, out_shape: [3, 2, 4]}, 8 | {axis: 2, start: 0, out_shape: [4, 2, 3]}, 9 | {axis: 1, start: 1, out_shape: [2, 3, 4]}, 10 | {axis: -3, start: 2, out_shape: [3, 2, 4]}, 11 | {axis: -1, start: 0, out_shape: [4, 2, 3]}, 12 | {axis: -2, start: -2, out_shape: [2, 3, 4]}, 13 | {axis: 0, start: 3, out_shape: [3, 4, 2]}, 14 | {axis: 2, start: -3, out_shape: [4, 2, 3]}, 15 | ], keep: true) 16 | 17 | def setup 18 | @dtype = xm::SFloat 19 | 20 | @axis = data[:test_case][:axis] 21 | @start = data[:test_case][:start] 22 | @out_shape = data[:test_case][:out_shape] 23 | 24 | @x = @dtype.new([2, 3, 4]).rand(-1, 1) 25 | @g = @dtype.new(@out_shape).rand(-1, 1) 26 | @gg = @dtype.new([2, 3, 4]).rand(-1, 1) 27 | end 28 | 29 | def check_forward(x_data) 30 | x = Chainer::Variable.new(x_data) 31 | y = Chainer::Functions::Array::Rollaxis.rollaxis(x, @axis, start: @start) 32 | 33 | expect = Chainer::Utils::Array.rollaxis(@x, @axis, start: @start) 34 | Chainer::Testing.assert_allclose(expect, y.data) 35 | end 36 | 37 | def test_forward 38 | check_forward(@x) 39 | end 40 | 41 | def check_backward(x_data, g_data) 42 | func = -> (x) do 43 | Chainer::Functions::Array::Rollaxis.rollaxis(x, @axis, start: @start) 44 | end 45 | 46 | Chainer::check_backward(func, x_data, g_data, dtype: xm::DFloat) 47 | end 48 | 49 | def test_backward 50 | check_backward(@x, @g) 51 | end 52 | 53 | def check_double_backward(x_data, g_data, gg_data) 54 | func = -> (x) do 55 | y = Chainer::Functions::Array::Rollaxis.rollaxis(x, @axis, start: @start) 56 | y * y 57 | end 58 | 59 | Chainer::check_double_backward(func, x_data, g_data, gg_data) 60 | end 61 | 62 | def test_double_backward_cpu 63 | check_double_backward(@x, @g, @gg) 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /test/functions/array/select_item_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'chainer/functions/array/select_item' 4 | 5 | class Chainer::Functions::Array::SelectItemTest < Test::Unit::TestCase 6 | data(:test_case, [ 7 | {in_shape: [10, 5], out_shape: [10]}, 8 | {in_shape: [0, 5], out_shape: [0]}, 9 | {in_shape: [1, 33], out_shape: [1]}, 10 | {in_shape: [10, 5], out_shape: [10]}, 11 | {in_shape: [10, 5], out_shape: [10]}, 12 | ], keep: true) 13 | data(:dtype, [xm::SFloat, xm::DFloat], keep: true) 14 | 15 | def setup 16 | @dtype = data[:dtype] 17 | @in_shape = data[:test_case][:in_shape] 18 | @out_shape = data[:test_case][:out_shape] 19 | 20 | @x_data = @dtype.new(@in_shape).rand(-1, 1) 21 | @t_data = xm::Int32.new(@out_shape).rand(0, 2) 22 | @gy_data = @dtype.new(@out_shape).rand(-1, 1) 23 | @ggx_data = @dtype.new(@in_shape).rand(-1, 1) 24 | 25 | @check_backward_options = {atol: 0.01, rtol: 0.01} 26 | end 27 | 28 | def check_forward(x_data, t_data) 29 | x = Chainer::Variable.new(x_data) 30 | t = Chainer::Variable.new(t_data) 31 | y = Chainer::Functions::Array::SelectItem.select_item(x, t) 32 | 33 | y_exp = x_data.class.zeros(t_data.size) 34 | t_data.size.times.each do |i| 35 | y_exp[i] = x_data[i, t_data[i].to_i] 36 | end 37 | 38 | assert_equal(@dtype, y.data.class) 39 | assert_equal(y_exp.to_a, y.data.to_a) 40 | end 41 | 42 | def test_forward 43 | check_forward(@x_data, @t_data) 44 | end 45 | 46 | def check_backward(x_data, t_data, gy_data) 47 | func = -> (x, t) do 48 | Chainer::Functions::Array::SelectItem.select_item(x, t) 49 | end 50 | Chainer::check_backward(func, [x_data, t_data], gy_data, dtype: xm::DFloat, eps: 0.01, **@check_backward_options) 51 | end 52 | 53 | def test_backward 54 | check_backward(@x_data, @t_data, @gy_data) 55 | end 56 | 57 | def check_double_backward(x_data, t_data, gy_data, ggx_data) 58 | func = -> (x) do 59 | y = Chainer::Functions::Array::SelectItem.select_item(x, t_data) 60 | y * y 61 | end 62 | 63 | Chainer::check_double_backward(func, x_data, gy_data, ggx_data, eps: 0.01, dtype: xm::DFloat, **@check_backward_options) 64 | end 65 | 66 | def test_double_backward 67 | check_double_backward(@x_data, @t_data, @gy_data, @ggx_data) 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /test/functions/array/squeeze_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'chainer/functions/array/squeeze' 4 | 5 | class Chainer::Functions::Array::SqueezeTest < Test::Unit::TestCase 6 | data(:test_case, [ 7 | { axis: nil, in_shape: [1, 3, 1, 3], out_shape: [3, 3] }, 8 | { axis: 0, in_shape: [1, 3, 1, 3], out_shape: [3, 1, 3] }, 9 | { axis: [2], in_shape: [1, 3, 1, 3], out_shape: [1, 3, 3] } 10 | ], keep: true) 11 | data(:dtype, [xm::SFloat, xm::DFloat], keep: true) 12 | 13 | def test_forward 14 | in_data = data[:dtype].new(data[:test_case][:in_shape]).seq 15 | x = Chainer::Variable.new(in_data) 16 | y = Chainer::Functions::Array::Squeeze.squeeze(x, axis: data[:test_case][:axis]) 17 | 18 | assert_equal(y.data.shape, data[:test_case][:out_shape]) 19 | assert_equal(y.dtype, data[:dtype]) 20 | end 21 | 22 | def test_backward 23 | in_data = data[:dtype].new(data[:test_case][:in_shape]).seq 24 | x = Chainer::Variable.new(in_data) 25 | y = Chainer::Functions::Array::Squeeze.squeeze(x, axis: data[:test_case][:axis]) 26 | y.grad = y.data 27 | y.backward 28 | 29 | Chainer::Testing.assert_allclose(x.data, x.grad, atol: 0, rtol: 0) 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /test/functions/array/transpose_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'chainer/functions/array/transpose' 4 | 5 | class Chainer::Functions::Array::TransposeTest < Test::Unit::TestCase 6 | data(:in_shape, [[4, 3, 2]], keep: true) 7 | data(:axes, [[0, 1], nil], keep: true) 8 | data(:dtype, [xm::SFloat, xm::DFloat], keep: true) 9 | 10 | def setup 11 | @x = data[:dtype].new(*data[:in_shape]).rand 12 | end 13 | 14 | def test_forward(data) 15 | x = Chainer::Variable.new(@x) 16 | y = Chainer::Functions::Array::Transpose.transpose(x, axes: data[:axes]) 17 | 18 | assert_equal(data[:dtype], y.dtype) 19 | assert_equal(@x.transpose(*data[:axes]), y.data) 20 | end 21 | 22 | def test_backward(data) 23 | x = Chainer::Variable.new(@x) 24 | 25 | y = Chainer::Functions::Array::Transpose.transpose(x, axes: data[:axes]) 26 | y.grad = y.data 27 | y.backward 28 | 29 | Chainer::Testing.assert_allclose(x.data, x.grad, atol: 0, rtol: 0) 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /test/functions/connection/convolution_2d_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Chainer::Functions::Connection::Convolution2DTest < Test::Unit::TestCase 4 | data(:cover_all, [true, false], keep: true) 5 | data(:x_dtype, [xm::SFloat], keep: true) 6 | data(:w_dtype, [xm::SFloat], keep: true) 7 | 8 | def setup 9 | in_channels = 3 10 | out_channels = 2 11 | kh, kw = [3, 3] 12 | 13 | @stride = 2 14 | @pad = 1 15 | 16 | @w = data[:w_dtype].new(out_channels, in_channels, kh, kw).rand_norm(0, xm::NMath.sqrt(1.0 / (kh * kw * in_channels))) 17 | @b = data[:x_dtype].new(out_channels).rand(-1, 1) 18 | 19 | @x = data[:x_dtype].new([2, 3, 4, 3]).rand(-1, 1) 20 | if data[:cover_all] 21 | @gy = data[:x_dtype].new([2, 2, 3, 2]).rand(-1 ,1) 22 | else 23 | @gy = data[:x_dtype].new([2, 2, 2, 2]).rand(-1, 1) 24 | end 25 | 26 | @ggx = data[:x_dtype].new(*@x.shape).rand(-1, 1) 27 | @ggw = data[:w_dtype].new(*@w.shape).rand(-1, 1) 28 | @ggb = data[:x_dtype].new(*@b.shape).rand(-1, 1) 29 | 30 | @check_forward_options = {} 31 | @check_backward_options = { dtype: xm::DFloat } 32 | @check_double_backward_options = { dtype: xm::DFloat } 33 | end 34 | 35 | def test_forward(data) 36 | x = Chainer::Variable.new(@x) 37 | w = Chainer::Variable.new(@w) 38 | b = Chainer::Variable.new(@b) 39 | 40 | y = Chainer::Functions::Connection::Convolution2DFunction.convolution_2d(x, w, b: b, stride: @stride, pad: @pad, cover_all: data[:cover_all]) 41 | assert_equal(data[:x_dtype], y.data.class) 42 | end 43 | 44 | def test_backward(data) 45 | args = [@x, @w] 46 | if @b 47 | args << @b 48 | end 49 | 50 | func = -> (*args) do 51 | x, w, b = args 52 | Chainer::Functions::Connection::Convolution2DFunction.convolution_2d(x, w, b: b, stride: @stride, pad: @pad, cover_all: data[:cover_all]) 53 | end 54 | 55 | Chainer::check_backward(func, args, @gy, **@check_backward_options) 56 | end 57 | 58 | def test_double_backward 59 | args = [@x, @w] 60 | grad_grads = [@ggx, @ggw] 61 | if @b 62 | args << @b 63 | grad_grads << @ggb 64 | end 65 | 66 | func = -> (*args) do 67 | x, w, b = args 68 | y = Chainer::Functions::Connection::Convolution2DFunction.convolution_2d(x, w, b: b, stride: @stride, pad: @pad, cover_all: data[:cover_all]) 69 | 70 | y * y # make the function nonlinear 71 | end 72 | 73 | Chainer::check_double_backward(func, args, [@gy], grad_grads, **@check_double_backward_options) 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /test/functions/connection/deconvolution_2d_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'chainer/functions/connection/deconvolution_2d' 3 | 4 | class Chainer::Functions::Connection::Deconvolution2DFunctionTest < Test::Unit::TestCase 5 | include Chainer::Functions::Connection 6 | 7 | data(:c_contiguous, [true], keep: true) 8 | data(:test_outsize, [true, false], keep: true) 9 | data(:nobias, [true, false], keep: true) 10 | data(:stride, [1, 2], keep: true) 11 | data(:x_dtype, [xm::SFloat, xm::DFloat], keep: true) 12 | data(:w_dtype, [xm::SFloat, xm::DFloat], keep: true) 13 | 14 | def setup 15 | in_channels = 3 16 | out_channels = 2 17 | ksize = 3 18 | @pad = 1 19 | 20 | kh, kw = [3, 3] 21 | sh, sw = data[:stride].is_a?(::Array) ? data[:stride] : [data[:stride], data[:stride]] 22 | ph, pw = [1, 1] 23 | 24 | @w = data[:w_dtype].new(in_channels, out_channels, kh, kw).rand_norm(0, xm::NMath.sqrt(1.0 / (kh * kw * in_channels)).to_f) 25 | @b = data[:nobias] ? nil : data[:x_dtype].new(out_channels).rand(-1, 1) 26 | 27 | n = 2 28 | inh, inw = 4, 3 29 | outh = Chainer::Utils::Conv.get_deconv_outsize(inh, kh, sh, ph) 30 | outw = Chainer::Utils::Conv.get_deconv_outsize(inw, kw, sw, pw) 31 | 32 | @outsize = data[:test_outsize] ? [outh, outw] : nil 33 | @x = data[:x_dtype].new(n, in_channels, inh, inw).rand(-1, 1) 34 | @gy = data[:x_dtype].new(n, out_channels, outh, outw).rand(-1, 1) 35 | 36 | @ggx = data[:x_dtype].new(*@x.shape).rand(-1, 1) 37 | @ggw = data[:w_dtype].new(*@w.shape).rand(-1, 1) 38 | @ggb = data[:nobias] ? nil : data[:x_dtype].new(*@b.shape).rand(-1, 1) 39 | 40 | @check_backward_options = { dtype: xm::DFloat } 41 | @check_double_backward_options = { dtype: xm::DFloat } 42 | end 43 | 44 | def test_forward(data) 45 | x = Chainer::Variable.new(@x) 46 | w = Chainer::Variable.new(@w) 47 | b = data[:nobias] ? nil : Chainer::Variable.new(@b) 48 | 49 | y = Deconvolution2DFunction.deconvolution_2d(x, w, b: b, stride: data[:stride], pad: @pad, outsize: @outsize) 50 | 51 | assert_equal(data[:x_dtype], y.data.class) 52 | end 53 | 54 | def test_backward(data) 55 | if @b.nil? 56 | args = [@x, @w] 57 | else 58 | args = [@x, @w, @b] 59 | end 60 | 61 | func = -> (*args) do 62 | if data[:nobias] 63 | x, w = args 64 | Deconvolution2DFunction.deconvolution_2d(x, w, stride: data[:stride], pad: @pad, outsize: @outsize) 65 | else 66 | x, w, b = args 67 | Deconvolution2DFunction.deconvolution_2d(x, w, b: b, stride: data[:stride], pad: @pad, outsize: @outsize) 68 | end 69 | end 70 | Chainer::check_backward(func, args, @gy, **@check_backward_options) 71 | end 72 | 73 | def test_double_backward 74 | args = [@x, @w] 75 | grad_grads = [@ggx, @ggw] 76 | if @b 77 | args << @b 78 | grad_grads << @ggb 79 | end 80 | 81 | func = -> (*args) do 82 | if data[:nobias] 83 | x, w = args 84 | y = Deconvolution2DFunction.deconvolution_2d(x, w, stride: data[:stride], pad: @pad, outsize: @outsize) 85 | else 86 | x, w, b = args 87 | y = Deconvolution2DFunction.deconvolution_2d(x, w, b: b, stride: data[:stride], pad: @pad, outsize: @outsize) 88 | end 89 | 90 | y * y # make the function nonlinear 91 | end 92 | 93 | Chainer::check_double_backward(func, args, [@gy], grad_grads, **@check_double_backward_options) 94 | end 95 | end 96 | -------------------------------------------------------------------------------- /test/functions/connection/linear_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'chainer/functions/connection/linear' 4 | 5 | class Chainer::Functions::Connection::LinearTest < Test::Unit::TestCase 6 | data(:dtype, [ xm::SFloat, xm::DFloat ], keep: true) 7 | 8 | def setup 9 | @w = data[:dtype].new(2, 3).rand 10 | @b = data[:dtype].new(2).rand 11 | 12 | @x = data[:dtype].new(4, 3).rand 13 | @gy = data[:dtype].new(4, 2).rand 14 | @ggx = data[:dtype].new(*@x.shape).rand 15 | @ggw = data[:dtype].new(*@w.shape).rand 16 | @ggb = data[:dtype].new(*@b.shape).rand 17 | 18 | @y = @x.dot(@w.transpose) + @b 19 | end 20 | 21 | def test_forward(data) 22 | x = Chainer::Variable.new(@x) 23 | w = Chainer::Variable.new(@w) 24 | b = Chainer::Variable.new(@b) 25 | y = Chainer::Functions::Connection::LinearFunction.linear(x, w, b) 26 | 27 | assert_equal(data[:dtype], y.data.class) 28 | 29 | y_expect = @x.dot(@w.transpose) + @b 30 | Chainer::Testing.assert_allclose(y_expect, y.data) 31 | end 32 | 33 | def test_backward(data) 34 | args = [@x, @w, @b] 35 | func = -> (x, w, b) { Chainer::Functions::Connection::LinearFunction.linear(x, w, b) } 36 | Chainer::check_backward(func, args, @gy) 37 | end 38 | 39 | def test_backward_nobias(data) 40 | args = [@x, @w] 41 | func = -> (x, w) { Chainer::Functions::Connection::LinearFunction.linear(x, w) } 42 | Chainer::check_backward(func, args, @gy) 43 | end 44 | 45 | def test_double_backward(data) 46 | args = [@x, @w, @b] 47 | grad_grads = [@ggx, @ggw, @ggb] 48 | 49 | func = -> (x, w, b) do 50 | y = Chainer::Functions::Connection::LinearFunction.linear(x, w, b) 51 | y * y 52 | end 53 | 54 | Chainer::check_double_backward(func, args, [@gy], grad_grads) 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /test/functions/loss/mean_squared_error_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'chainer/functions/loss/mean_squared_error' 4 | 5 | class Chainer::Functions::Loss::MeanSquaredErrorTest < Test::Unit::TestCase 6 | 7 | def setup 8 | @x0 = xm::SFloat.new([4, 3]).rand(-1, 1) 9 | @x1 = xm::SFloat.new([4, 3]).rand(-1, 1) 10 | @gy = xm::SFloat.new.rand(-1, 1) 11 | @ggx0 = xm::SFloat.new(4, 3).rand(-1, 1) 12 | @ggx1 = xm::SFloat.new(4, 3).rand(-1, 1) 13 | end 14 | 15 | def check_forward(x0_data, x1_data) 16 | x0 = Chainer::Variable.new(x0_data) 17 | x1 = Chainer::Variable.new(x1_data) 18 | loss = Chainer::Functions::Loss::MeanSquaredError.mean_squared_error(x0, x1) 19 | loss_value = loss.data 20 | assert_equal(xm::SFloat, loss_value.class) 21 | assert_equal([], loss_value.shape) 22 | loss_expect = 0.0 23 | @x0.each_with_index{|x,*i| loss_expect += (@x0[*i] - @x1[*i]) ** 2} 24 | loss_expect = (loss_expect)/(@x0.size).to_f 25 | assert_in_delta(loss_expect, loss_value, 0.00001) 26 | end 27 | 28 | def test_forward 29 | check_forward(@x0, @x1) 30 | end 31 | 32 | def check_backward(x0_data, x1_data) 33 | Chainer::check_backward(Chainer::Functions::Loss::MeanSquaredError.method(:mean_squared_error), [x0_data, x1_data], nil, eps: 0.01) 34 | end 35 | 36 | def test_backward 37 | check_backward(@x0, @x1) 38 | end 39 | 40 | def check_double_backward(x0_data, x1_data, gy_data, ggx0_data, ggx1_data) 41 | func = Chainer::Functions::Loss::MeanSquaredError.method(:mean_squared_error) 42 | Chainer::check_double_backward(func, [x0_data, x1_data], gy_data, [ggx0_data, ggx1_data], eps: 1e-2) 43 | end 44 | 45 | def test_double_backward 46 | check_double_backward(@x0, @x1, @gy, @ggx0, @ggx1) 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /test/functions/math/basic_math_test.rb: -------------------------------------------------------------------------------- 1 | require 'chainer' 2 | require 'numo/narray' 3 | 4 | class Chainer::Functions::Math::BasicMathTest < Test::Unit::TestCase 5 | test("Neg#forward") do 6 | x = Chainer::Variable.new(xm::DFloat[[-1, 0],[1, 2]]) 7 | assert_equal(xm::DFloat[[1,0],[-1,-2]], (-x).data) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/functions/math/exp_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'chainer/functions/math/sum' 4 | 5 | class Chainer::Functions::Math::ExpTest < Test::Unit::TestCase 6 | data(:shape, [[3, 2], []], keep: true) 7 | data(:dtype, [xm::SFloat, xm::DFloat], keep: true) 8 | 9 | def setup 10 | @x = data[:dtype].new(data[:shape]).rand(-1, 1) 11 | @gy = data[:dtype].new(data[:shape]).rand(-1, 1) 12 | @ggy = data[:dtype].new(data[:shape]).rand(-1, 1) 13 | end 14 | 15 | def check_forward(x_data) 16 | x = Chainer::Variable.new(x_data) 17 | y = Chainer::Functions::Math::Exp.exp(x) 18 | 19 | assert_equal(x.data.class, y.data.class) 20 | 21 | Chainer::Testing.assert_allclose(xm::NMath.exp(@x), y.data, atol: 1e-7, rtol: 1e-7) 22 | end 23 | 24 | def test_forward 25 | check_forward(@x) 26 | end 27 | 28 | def check_backward(x_data, y_grad) 29 | func = -> (x) do 30 | Chainer::Functions::Math::Exp.exp(x) 31 | end 32 | 33 | Chainer::check_backward(func, x_data, y_grad, atol: 1e-4, rtol: 1e-3, dtype: xm::DFloat) 34 | end 35 | 36 | def test_backward 37 | check_backward(@x.dup, @gy.dup) 38 | end 39 | 40 | def check_double_backward(x_data, y_grad, x_grad_grad) 41 | func = -> (x) do 42 | Chainer::Functions::Math::Exp.exp(x) 43 | end 44 | 45 | Chainer::check_double_backward(func, x_data, y_grad, x_grad_grad, atol: 1e-4, rtol: 1e-3, dtype: xm::DFloat) 46 | end 47 | 48 | def test_double_backward 49 | check_double_backward(@x, @gy, @ggy) 50 | end 51 | 52 | def test_label 53 | label = Chainer::Functions::Math::Exp.new.label 54 | assert_equal('exp', label) 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /test/functions/math/sum_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'chainer/functions/math/sum' 4 | 5 | class Chainer::Functions::Math::SumTest < Test::Unit::TestCase 6 | data(:axis, [nil, 0, 1, 2, -1, [0, 1], [1, 0], [0, -1], [-2, 0]], keep: true) 7 | data(:keepdims, [true, false], keep: true) 8 | data(:dtype, [ xm::SFloat, xm::DFloat ], keep: true) 9 | 10 | def test_forward(data) 11 | x_data = data[:dtype].new([3, 2, 4]).rand 12 | x = Chainer::Variable.new(x_data) 13 | y = Chainer::Functions::Math::Sum.sum(x, axis: data[:axis], keepdims: data[:keepdims]) 14 | assert_equal(y.data.class, data[:dtype]) 15 | 16 | y_expect = x_data.sum(axis: data[:axis], keepdims: data[:keepdims]) 17 | Chainer::Testing.assert_allclose(y_expect, y.data, atol: 0, rtol: 0) 18 | end 19 | 20 | def test_backward(data) 21 | x = data[:dtype].new([3, 2, 4]).rand 22 | 23 | g = x.sum(axis: data[:axis], keepdims: data[:keepdims]) 24 | g_shape = g.is_a?(xm::NArray) ? g.shape : [] 25 | gy = data[:dtype].new(g_shape).rand 26 | 27 | func = lambda{ |x| Chainer::Functions::Math::Sum.sum(x, axis: data[:axis], keepdims: data[:keepdims]) } 28 | Chainer::check_backward(func, x, gy, atol: 1e-4, dtype: xm::DFloat) 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /test/functions/noise/dropout_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Chainer::Functions::Noise::DropoutTest < Test::Unit::TestCase 4 | data(:dtype, [xm::SFloat, xm::DFloat], keep: true) 5 | data(:ratio, [0.0, 0.3, 0.5], keep: true) 6 | 7 | def setup 8 | @dtype = data[:dtype] 9 | @ratio = data[:ratio] 10 | 11 | @x = @dtype.new([2, 3]).rand(-1, 1) 12 | @gy = @dtype.new([2, 3]).rand(-1, 1) 13 | @ggx = @dtype.new([2, 3]).rand(-1, 1) 14 | 15 | @check_backward_options = { dtype: xm::DFloat } 16 | @check_double_backward_options = { dtype: xm::DFloat } 17 | end 18 | 19 | def check_forward(x_data) 20 | x = Chainer::Variable.new(x_data) 21 | y = Chainer::Functions::Noise::Dropout.dropout(x, ratio: @ratio) 22 | if @ratio == 0.0 23 | y_expect = x_data 24 | else 25 | y_expect = x_data * y.creator_node.mask 26 | end 27 | Chainer::Testing.assert_allclose(y_expect, y.data) 28 | end 29 | 30 | def test_forward 31 | check_forward(@x) 32 | end 33 | 34 | def check_backward(x_data, y_grad) 35 | dropout = Chainer::Functions::Noise::Dropout.new(@ratio) 36 | f = -> (x) do 37 | dropout.apply([x]).first 38 | end 39 | Chainer::check_backward(f, x_data, y_grad, **@check_backward_options) 40 | end 41 | 42 | def test_backward 43 | check_backward(@x, @gy) 44 | end 45 | 46 | def check_double_backward(x_data, y_grad, x_grad_grad) 47 | dropout = Chainer::Functions::Noise::Dropout.new(@ratio) 48 | f = -> (x) do 49 | x, = dropout.apply([x]) 50 | x * x 51 | end 52 | Chainer::check_double_backward(f, x_data, y_grad, x_grad_grad, **@check_double_backward_options) 53 | end 54 | 55 | def test_double_backward 56 | check_double_backward(@x, @gy, @ggx) 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /test/functions/pooling/average_pooling_2d_test.rb: -------------------------------------------------------------------------------- 1 | class Chainer::Functions::Pooling::AveragePooling2DTest < Test::Unit::TestCase 2 | data(:dtype, [xm::SFloat, xm::DFloat], keep: true) 3 | 4 | def setup 5 | @dtype = data[:dtype] 6 | 7 | @x = @dtype.new([2, 3, 4, 3]).rand(-1, 1) 8 | @gy = @dtype.new([2, 3, 2, 2]).rand(-1, 1) 9 | @check_forward_options = {} 10 | @check_backward_options = { dtype: xm::DFloat } 11 | @ggx = @dtype.new([2, 3, 4, 3]).rand(-1, 1) 12 | end 13 | 14 | def check_forward(x_data, use_cudnn: 'always') 15 | x = Chainer::Variable.new(x_data) 16 | y = Chainer::Functions::Pooling::AveragePooling2D.average_pooling_2d(x, 3, stride: 2, pad: 1) 17 | assert_equal(y.data.class, @dtype) 18 | 19 | y_data = y.data 20 | assert_equal(@gy.shape, y_data.shape) 21 | 2.times.each do |k| 22 | 3.times.each do |c| 23 | x = @x[k, c, false] 24 | expect = xm::DFloat[[x[0...2, 0...2, false].sum.to_f, x[0...2, 1...3, false].sum.to_f], [x[1...4, 0...2, false].sum.to_f, x[1...4, 1...3, false].sum.to_f]] / 9 25 | Chainer::Testing.assert_allclose(expect, y_data[k, c, false], **@check_forward_options) 26 | end 27 | end 28 | end 29 | 30 | def test_forward 31 | check_forward(@x) 32 | end 33 | 34 | def check_backward(x_data, y_grad, use_cudnn: 'always') 35 | func = -> (x) do 36 | Chainer::Functions::Pooling::AveragePooling2D.average_pooling_2d(x, 3, stride: 2, pad: 1) 37 | end 38 | 39 | Chainer::check_backward(func, x_data, y_grad, **@check_backward_options) 40 | end 41 | 42 | def test_backward 43 | check_backward(@x.dup, @gy.dup) 44 | end 45 | 46 | def check_double_backward(x_data, y_grad, x_grad_grad, use_cudnn: 'always') 47 | func = -> (x) do 48 | y = Chainer::Functions::Pooling::AveragePooling2D.average_pooling_2d(x, 3, stride: 2, pad: 1) 49 | y * y 50 | end 51 | Chainer::check_double_backward(func, x_data, y_grad, x_grad_grad, **@check_backward_options) 52 | end 53 | 54 | def test_double_backward 55 | check_double_backward(@x, @gy, @ggx) 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /test/functions/pooling/max_pooling_2d_test.rb: -------------------------------------------------------------------------------- 1 | class Chainer::Functions::Pooling::MaxPooling2DTest < Test::Unit::TestCase 2 | data(:dtype, [xm::SFloat, xm::DFloat], keep: true) 3 | data(:cover_all, [true, false], keep: true) 4 | 5 | def setup 6 | @dtype = data[:dtype] 7 | @cover_all = data[:cover_all] 8 | 9 | x = @dtype.new(2, 3, 4, 3).seq.to_a.shuffle 10 | @x = @dtype[*x] 11 | @x = 2 * @x / @x.size - 1 12 | 13 | if @cover_all 14 | @gy = @dtype.new(2, 3, 3, 2).rand(-1, 1) 15 | else 16 | @gy = @dtype.new(2, 3, 2, 2).rand(-1, 1) 17 | end 18 | 19 | @ggx = @dtype.new(2, 3, 4, 3).rand(-1, 1) 20 | end 21 | 22 | def check_forward(x_data, use_cudnn: 'always') 23 | x = Chainer::Variable.new(x_data) 24 | y = Chainer::Functions::Pooling::MaxPooling2D.max_pooling_2d(x, 3, stride: 2, pad: 1, cover_all: @cover_all) 25 | assert_equal(y.data.class, @dtype) 26 | 27 | y_data = y.data 28 | assert_equal(@gy.shape, y_data.shape) 29 | 30 | 2.times.each do |k| 31 | 3.times.each do |c| 32 | x = @x[k, c, false] 33 | if @cover_all 34 | expect = xm::DFloat[ 35 | [x[0...2, 0...2, false].max.to_f, x[0...2, 1...3, false].max.to_f], 36 | [x[1...4, 0...2, false].max.to_f, x[1...4, 1...3, false].max.to_f], 37 | [x[1...4, 0...2, false].max.to_f, x[3...4, 1...3, false].max.to_f], 38 | ] 39 | else 40 | expect = xm::DFloat[ 41 | [x[0...2, 0...2, false].max.to_f, x[0...2, 1...3, false].max.to_f], 42 | [x[1...4, 0...2, false].max.to_f, x[1...4, 1...3, false].max.to_f], 43 | ] 44 | end 45 | Chainer::Testing.assert_allclose(expect, y_data[k, c, false]) 46 | end 47 | end 48 | end 49 | 50 | def test_forward 51 | check_forward(@x) 52 | end 53 | 54 | def check_backward(x_data, y_grad, use_cudnn: 'always') 55 | func = -> (x) do 56 | Chainer::Functions::Pooling::MaxPooling2D.max_pooling_2d(x, 3, stride: 2, pad: 1, cover_all: @cover_all) 57 | end 58 | Chainer::check_backward(func, x_data, y_grad, dtype: xm::DFloat, atol: 1e-4, rtol: 1e-3) 59 | end 60 | 61 | def test_backward 62 | check_backward(@x.dup, @gy.dup) 63 | end 64 | 65 | def check_double_backward(x_data, y_grad, x_grad_grad, use_cudnn: 'always') 66 | func = -> (x) do 67 | y = Chainer::Functions::Pooling::MaxPooling2D.max_pooling_2d(x, 3, stride: 2, pad: 1, cover_all: @cover_all) 68 | y * y 69 | end 70 | Chainer::check_double_backward(func, x_data, y_grad, x_grad_grad, dtype: xm::DFloat, atol: 1e-4, rtol: 1e-3) 71 | end 72 | 73 | def test_double_backward 74 | check_double_backward(@x, @gy, @ggx) 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /test/initializers/uniform_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'chainer/initializers/uniform' 4 | 5 | class Chainer::Initializers::UniformTest < Test::Unit::TestCase 6 | 7 | data(:shape, [[2, 3], [2, 3, 4]], keep: true) 8 | data(:dtype, [ xm::SFloat, xm::DFloat ], keep: true) 9 | 10 | def test_initializer 11 | w = data[:dtype].new(data[:shape]) 12 | initializer = Chainer::Initializers::Uniform.new(scale: 0.1) 13 | w = initializer.(w) 14 | assert_equal(w.shape, data[:shape]) 15 | assert_equal(w.class, data[:dtype]) 16 | end 17 | 18 | def test_shaped_initializer 19 | initializer = Chainer::Initializers::Uniform.new(scale: 0.1, dtype: data[:dtype]) 20 | w = Chainer::Initializers.generate_array(initializer, data[:shape]) 21 | assert_equal(w.shape, data[:shape]) 22 | assert_equal(w.class, data[:dtype]) 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /test/links/connection/convolution_2d_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Chainer::Links::Connection::Convolution2DTest < Test::Unit::TestCase 4 | data({ 5 | test1: { 6 | case: { x: xm::DFloat.new(1, 3, 10, 10).seq, in_channels: 3, out_channels: 7, ksize: 5, options: {} }, 7 | expected: [1, 7, 6, 6] 8 | }, 9 | test2: { 10 | case: { x: xm::DFloat.new(3, 3, 6, 6).seq, in_channels: nil, out_channels: 4, ksize: 3, options: { stride: 3, pad: 3, initial_w: xm::DFloat.new(4, 3, 3, 3).seq } }, 11 | expected: [3, 4, 4, 4] 12 | }, 13 | }) 14 | def test_get_conv_outsize(data) 15 | test_case = data[:case] 16 | l = Chainer::Links::Connection::Convolution2D.new(test_case[:in_channels], test_case[:out_channels], test_case[:ksize], **test_case[:options]) 17 | assert_equal(data[:expected], l.(test_case[:x]).shape) 18 | end 19 | end 20 | 21 | -------------------------------------------------------------------------------- /test/links/connection/embed_id_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Chainer::Links::Connection::EmbedIDTest < Test::Unit::TestCase 4 | data = { 5 | test1: {x_data: [0, 1, 0], ignore_label: nil}, 6 | test2: {x_data: [[0, 1, 0], [1, 0, 1]], ignore_label: nil}, 7 | test3: {x_data: [0, 1, -1], ignore_label: -1}, 8 | test4: {x_data: [[0, 1, -1], [-1, 0, 1]], ignore_label: -1}, 9 | } 10 | 11 | def setup 12 | @link = Chainer::Links::Connection::EmbedID.new(3, 2, ignore_label: data[:ignore_label]) 13 | @link.cleargrads 14 | 15 | @w = @link.w.data.copy() 16 | @x = xm::Int32[*data[:x_data]] 17 | y_shape = @x.shape + [2] 18 | @gy = xm::SFloat.new(*y_shape).rand(-1, 1) 19 | end 20 | 21 | data(data) 22 | def test_forward(data) 23 | x = Chainer::Variable.new(xm::Int32.cast(data[:x_data])) 24 | y = @link.(x) 25 | assert_equal(y.data.class, xm::SFloat) 26 | y_expect = @gy.dup 27 | 28 | @x.shape.reduce(&:*).times do |i| 29 | ndindex = @x.shape.size.times.reduce([]) do |ndi, j| 30 | ndi << (i / @x.shape.drop(j+1).reduce(1, &:*)) % @x.shape[j] 31 | end 32 | y_expect[*ndindex, true] = @x[*ndindex] == -1 ? 0 : @w[@x[*ndindex].to_i, true] 33 | end 34 | 35 | assert_equal(y_expect.to_a, y.data.to_a) 36 | end 37 | 38 | data(data) 39 | def test_backward(data) 40 | Chainer::check_backward(@link, @x, @gy, @link.w, atol: 1e-4) 41 | end 42 | end 43 | 44 | -------------------------------------------------------------------------------- /test/optimizer_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'chainer' 4 | 5 | class SimpleLink < Chainer::Link 6 | attr_reader :param 7 | 8 | def initialize(w, g) 9 | super() 10 | init_scope do 11 | @param = Chainer::Parameter.new(initializer: w) 12 | @param.grad = g 13 | end 14 | end 15 | end 16 | 17 | class Chainer::WeightDecayTest < Test::Unit::TestCase 18 | def setup 19 | @target = SimpleLink.new( 20 | xm::SFloat.new(2, 3).seq, 21 | xm::SFloat.new(6).seq(-2).reverse.reshape(2, 3) 22 | ) 23 | end 24 | 25 | def check_weight_decay 26 | w = @target.param.data 27 | g = @target.param.grad 28 | 29 | decay = 0.2 30 | expect = w - g - decay * w 31 | 32 | opt = Chainer::Optimizers::MomentumSGD.new(lr: 1) 33 | opt.setup(@target) 34 | opt.add_hook(Chainer::WeightDecay.new(decay)) 35 | opt.update() 36 | Chainer::Testing.assert_allclose(expect, @target.param.data) 37 | end 38 | 39 | def test_weight_decay 40 | check_weight_decay 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /test/optimizers/momentum_sgd_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Chainer::Optimizers::MomentumSGDTest < Test::Unit::TestCase 4 | data({ 5 | test1: { 6 | case: { lr: nil, momentum: nil }, 7 | expected: xm::DFloat[0.96, 1.95, 2.94] 8 | }, 9 | test2: { 10 | case: { lr: 0.05, momentum: 0.5 }, 11 | expected: xm::DFloat[0.8 , 1.75, 2.7] 12 | } 13 | }) 14 | def test_momentum_sgd(data) 15 | var = Chainer::Variable.new(xm::DFloat[1, 2, 3]) 16 | var.grad = xm::DFloat[4, 5, 6] 17 | sgd = Chainer::Optimizers::MomentumSGD.new(lr: data[:case][:lr], momentum: data[:case][:momentum]) 18 | opt = sgd.create_update_rule 19 | opt.instance_variable_set(:@state, {}) 20 | opt.init_state(var) 21 | opt.update_core(var) 22 | assert_equal(data[:expected], var.data) 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /test/parameter_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'chainer' 4 | 5 | class TestUninitializedParameter < Test::Unit::TestCase 6 | def setup 7 | @a = xm::SFloat.new(3, 2).rand 8 | @b = @a.class.new(*@a.shape).rand 9 | end 10 | 11 | def test_initialize_node 12 | initializer = Chainer::Initializers::Normal.new(dtype: xm::DFloat) 13 | x = Chainer::Parameter.new(initializer: initializer) 14 | x.init([2, 3]) 15 | assert_equal([2, 3], x.node.shape) 16 | assert_equal(xm::DFloat, x.node.dtype) 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /test/run_test.rb: -------------------------------------------------------------------------------- 1 | base_dir = File.expand_path(File.join(File.dirname(__FILE__), "..")) 2 | lib_dir = File.join(base_dir, "lib") 3 | test_dir = File.join(base_dir, "test") 4 | 5 | $LOAD_PATH.unshift(lib_dir) 6 | 7 | require 'test/unit' 8 | require 'chainer' 9 | 10 | def require_gpu(id = nil) 11 | omit(['GPU', id, 'is needed'].join(' ')) unless Chainer::CUDA.available?(id) 12 | end 13 | 14 | def xm 15 | Chainer::Device.default.xm 16 | end 17 | 18 | device = Chainer::Device.create(Integer(ENV['RED_CHAINER_GPU'] || -1)) 19 | Chainer::Device.change_default(device) 20 | 21 | exit Test::Unit::AutoRunner.run(true, test_dir) 22 | -------------------------------------------------------------------------------- /test/training/extensions/exponential_shift_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Chainer::Training::Extensions::ExponentialShiftTest < Test::Unit::TestCase 4 | data({ 5 | test1: { 6 | case: { rate: 0.5, options: {} }, 7 | expected: 0.005 8 | }, 9 | test2: { 10 | case: { rate: 0.4, options: { init: 2.0 } }, 11 | expected: 0.8 12 | } 13 | }) 14 | def test_exponential_shift(data) 15 | model = Chainer::Links::Model::Classifier.new(1) 16 | optimizer = Chainer::Optimizers::MomentumSGD.new 17 | optimizer.setup(model) 18 | train_iter = Chainer::Iterators::SerialIterator.new(xm::DFloat[1, 2, 3], 1) 19 | updater = Chainer::Training::StandardUpdater.new(train_iter, optimizer) 20 | trainer = Chainer::Training::Trainer.new(updater) 21 | extension = Chainer::Training::Extensions::ExponentialShift.new("lr", data[:case][:rate], **data[:case][:options]) 22 | extension.init(trainer) 23 | extension.(trainer) 24 | 25 | assert_equal(data[:expected], optimizer.lr) 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /test/utils/math_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Chainer::Utils::MathTest < Test::Unit::TestCase 4 | data({ 5 | test1: { 6 | case: { 7 | a: xm::DFloat.new(2, 5, 2).seq, 8 | b: xm::DFloat.new(5, 2).seq, 9 | axes: 2 10 | }, 11 | expected: xm::DFloat[285.0, 735.0] 12 | }, 13 | test2: { 14 | case: { 15 | a: xm::DFloat.new(1, 3, 4).seq, 16 | b: xm::DFloat.new(5, 3).seq, 17 | axes: [1, 1] 18 | }, 19 | expected: xm::DFloat[[[20.0, 56.0, 92.0, 128.0, 164.0], [23.0, 68.0, 113.0, 158.0, 203.0], [26.0, 80.0, 134.0, 188.0, 242.0], [29.0, 92.0, 155.0, 218.0, 281.0]]] 20 | }, 21 | test3: { 22 | case: { 23 | a: xm::DFloat.new(1, 3, 4, 2).seq, 24 | b: xm::DFloat.new(1, 3, 1, 4).seq, 25 | axes: [[1, 2], [1, 3]] 26 | }, 27 | expected: xm::DFloat[[[[1012.0]], [[1078.0]]]] 28 | } 29 | }) 30 | def test_tensordot(data) 31 | actual = Chainer::Utils::Math.tensordot(data[:case][:a], data[:case][:b], data[:case][:axes]) 32 | assert_equal(data[:expected], actual) 33 | 34 | end 35 | end 36 | --------------------------------------------------------------------------------