LoginSignup
1
1

More than 1 year has passed since last update.

Chainer チュートリアル学習記録 Python篇

Last updated at Posted at 2021-09-06

チュートリアル学習の背景

  • 現在、機械学習の勉強をしているが、本に書かれているコードについて解説を読めばなんとなくやっていることは分かるが、自分でそのコードを1から書けと言われても書けない状況を打開したいと思っていた
  • pandasのSeriesとDataFrameの違いや要素へのアクセスなど、その都度調べる勉強方法がいいのか、一度、pandasやnumpyなどのチュートリアルをやってみたほうがいいのか考え中
  • 「その都度」のメリット・デメリット
    • メリット
      • その都度調べるから必要な量だけを実践的に知ることができる
    • デメリット
      • 体系的に身につかない
  • 「チュートリアル」のメリット・デメリット
    • メリット
      • 体系的に身につく
      • 良い資料(本当に必要なだけの内容を計算している)を使えば、体系的な学習のデメリットである時間がかかることを後々回収できる可能性がある
    • デメリット
      • 悪い資料(使用頻度の低いものまで掲載している)を使うと、体系的学習に時間と労力がかかる



色々と考えたが、時間があること、新卒一年目なので基礎からやっていたほうが時間がかかっても後々回収できること、チュートリアルがChainerの開発を過去に手掛けていた株式会社Preferred Networksが公開した資料であることから、機械学習の勉強を一旦ストップして、チュートリアル学習をすることに決めた。


参考文献:
ディープラーニング入門Chainer チュートリアル



以下、自分の理解が微妙な部分だけ抜粋して行った記録である。

エスケープシーケンス

print('hello\nworld')
hello
world
print('hello\tworld')
hello   world

文字列メソッド

name = 'akihiro'
type(name)
str
name = name.upper()
name
'AKIHIRO'
name = name.lower()
name
'akihiro'
print('{}、チュートリアルへようこそ'.format(name))
akihiro、チュートリアルへようこそ

浮動小数点メソッド

0.5.as_integer_ratio()
(1, 2)
0.25.as_integer_ratio()
(1, 4)

複合データ型

リスト・・・編集可能

nums = [1,2,3,4,5]
len(nums)
5
type(nums)
list
nums[1]
2
nums[0]
1
nums[:5]
[1, 2, 3, 4, 5]
nums[0:4]
[1, 2, 3, 4]
nums[0:]
[1, 2, 3, 4, 5]
nums[-1]
5
nums[-2]
4
nums[-4]
2
nums[:]
[1, 2, 3, 4, 5]
# 文字列と数値列の両方を同時に格納できる
arr = [[2020, 'Tokyo'], [2024, 'Pari']]
arr
[[2020, 'Tokyo'], [2024, 'Pari']]
arr.append([2016, 'Rio'])
arr
[[2020, 'Tokyo'], [2024, 'Pari'], [2016, 'Rio']]
#空のリストを用意して要素を追加
list = []
list.append('Chainer')
list.append('チュートリアル')
list
['Chainer', 'チュートリアル']

タプル・・・中の要素を変更できない

arr = (1,2,3,4,5)
arr
(1, 2, 3, 4, 5)
type(arr)
tuple
arr[3]
4
arr[:]
(1, 2, 3, 4, 5)

先述の通り、タプルは各要素の値を変更することができません。 この性質は、定数項などプログラムの途中で書き換わってしまうことが望ましくないものをまとめて扱うのに便利です。

# 値を変えようとするとエラーとなる
arr[0] = 10
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-48-668a784d059e> in <module>
      1 # 値を変えようとするとエラーとなる
----> 2 arr[0] = 10


TypeError: 'tuple' object does not support item assignment

tuple のように中身が変更できない性質のことをイミュータブル (immutable)であると言います。反対に、list のように中身が変更できる性質のことをミュータブル (mutable)であると言います

辞書

リストやタプルでは、各要素にアクセスする際に整数のインデックスを用いていましたが、辞書ではキーでインデックス化されているため、整数や文字列など、色々なものを使って要素を指定することができます。

scores = {'Math': 40, 'English': 89, 'Japanese': 76}
scores
{'Math': 40, 'English': 89, 'Japanese': 76}
type(scores)
dict
scores['Math']
40
scores['English']
89

他の人が定義した辞書に、どのようなキーが存在するのかを調べたいときがあります。 辞書には、そのような場合に使える便利なメソッドがいくつか存在します。
- keys(): キーのリストを取得。dict_keys というリストと性質が似た型が返る
- values(): 値のリストを取得。dict_values というリストと性質が似た型が返る
- items(): 各要素の (key, value) のタプルが並んだリストを取得。dict_items というリストと性質が似た型が返る

scores.keys()
dict_keys(['Math', 'English', 'Japanese'])
scores.values()
dict_values([40, 89, 76])
scores.items()
dict_items([('Math', 40), ('English', 89), ('Japanese', 76)])

dict_keys, dict_values, dict_items と新しい型が登場しましたが、これは辞書型特有の型であり厳密には標準のリストとは異なりますが、リストと性質の似た型であるという程度の認識で問題ありません。

辞書に要素を追加する場合は、新しいキーを指定して値を代入します。

scores['Science'] = 99
scores
{'Math': 40, 'English': 89, 'Japanese': 76, 'Science': 99}
scores['Math'] = 55
scores
{'Math': 55, 'English': 89, 'Japanese': 76, 'Science': 99}

制御構文

繰り返し(for文)

names = ['akihiro', 'tomohiro', 'kiyomi', 'atsushi', 'coco']

for i in range(5):
    print(names[i])
akihiro
tomohiro
kiyomi
atsushi
coco
for i in range(5):
    print('{}さん'.format(names[i]))
akihiroさん
tomohiroさん
kiyomiさん
atsushiさん
cocoさん
for i in range(len(names)):
    print('{}さん'.format(names[i]))
akihiroさん
tomohiroさん
kiyomiさん
atsushiさん
cocoさん

これでリストの要素数に依存しないプログラムにすることができました。

また、リスト自体をイテラブルオブジェクトとして指定することにより、リスト要素数の取得も [] でのインデックス番号の指定もせずに、より可読性の高いプログラムを書くことができます。

for name in names:
    print('{}さん'.format(name))
akihiroさん
tomohiroさん
kiyomiさん
atsushiさん
cocoさん

リストをイテラブルオブジェクトとして指定した場合、要素番号を取得できませんが、状況によっては要素番号を使用したいことがあります。

そのような場合は、enumerate() という組み込み関数を使います。 これにイテラブルオブジェクトを渡すと、(要素番号, 要素) というタプルを 1 つずつ返すイテラブルオブジェクトになります。

for i, name in enumerate(names):
    message = '{}番目:{}さん'.format(i, name)
    print(message)
0番目:akihiroさん
1番目:tomohiroさん
2番目:kiyomiさん
3番目:atsushiさん
4番目:cocoさん

enumerate() と同様、for 文と合わせてよく使う組み込み関数に zip() があります。

zip() は、複数のイテラブルオブジェクトを受け取り、その要素のペアを順番に返すイテラブルオブジェクトを作ります。 このイテラブルオブジェクトは、渡されたイテラブルオブジェクトそれぞれの先頭の要素から順番に、タプルに束ねて返します。 このイテラブルオブジェクトの長さは、渡されたイテラブルオブジェクトのうち最も短い長さと一致します。

names = ['Python', 'Chainer']
versions = ['3.7', '5.3.0']
suffixes = ['!!', '!!', '?']

for name, version, suffix in zip(names, versions, suffixes):
    print('{} {} {}'.format(name, version, suffix))
Python 3.7 !!
Chainer 5.3.0 !!

suffixes の要素数は 3 ですが、より短いイテラブルオブジェクトと共に zip に渡されたため、先頭から 2 つ目までしか値が取り出されていません。


関数

関数の引数のデフォルト値

def hello(message='Chainerチュートリアルへようこそ!'):
    print(message)
hello()
Chainerチュートリアルへようこそ!
hello('Welcome to Chainer tutorial!') # 上書きが可能
Welcome to Chainer tutorial!

変数のスコープ

a = 1

def change():
    a = 2
    print('inside a = ', a)

change()
print('outside a = ', a)
inside a =  2
outside a =  1
a = 1 
def change1():
    print('from inside:', a)
    # 関数内で定義していないため、関数外の変数を参照することは出来る
change1()

print('from outside:', a)
from inside: 1
from outside: 1

関数の外で定義された変数はグローバル変数と呼ばれます。 グローバル変数は、特に特別な記述を要せず参照することはできますが、関数の中で代入を行う場合は、global 文を使って、代入先をグローバル変数とする宣言を行う必要があります。


a = 1 
def change():
    global a
    a = 2

change()
a  
2

クラス

class HOUSE:
    def __init__(self, name):
        self.name_plate = name

init() はそういったメソッドの一つで、インスタンス化する際に自動的に呼ばれるメソッドです。

House クラスの init() は、name という引数をとり、これを self.name_plate という変数に代入しています。 この self というのは、クラスがインスタンス化されたあと、作成されたインスタンス自身を参照するのに用いられます。 これを使って、self.name_plate = name とすることで、作成された個別のインスタンスに属する変数 self.name_plate へ、引数に渡された name が持つ値を代入することができます。 self が指すものは、各インスタンスから見た「自分自身」なので、各インスタンスごとに異なります。 これによって、self.name_plate は各インスタンスに紐付いた別々の値を持つものとなります。

my_house = HOUSE('Chainer')

House というクラスの init() メソッドに、'Chainer' という文字列を渡しています。 my_house が、House クラスから作成されたインスタンスです。 ここで、クラス定義では init() メソッドは self と name という 2 つの引数をとっていましたが、呼び出しの際には 'Chainer' という一つの引数しか与えていませんでした。 この 'Chainer' という文字列は、1 つ目の引数であるにも関わらず、init() メソッドの定義では 2 つ目の引数であった name に渡されます。 前述のように、メソッドは、インスタンスから呼び出されるとき自動的に第一引数にそのインスタンスへの参照を渡すためです。 この自動的に渡される自身への参照は、呼び出しの際には明示的に指定しません。 また、かならず 1 つ目の引数に自動的に渡されるため、呼び出し時に明示的に与えられた引数は 2 つ目以降の引数に渡されたものとして取り扱われます。

class HOUSE:
    def __init__(self, name):
        self.name_plate = name

    def hello(self):
        print('{}さんの家です'.format(self.name_plate))
sato = HOUSE('Sato')
suzuki = HOUSE('Suzuki')

sato.hello()
suzuki.hello()
Satoさんの家です
Suzukiさんの家です

このように、同じ機能を持つが、インスタンスによって保持するデータが異なったり、一部の動作が異なったりするようなケースを扱うのにクラスを利用します。 Python の int 型、float 型、str 型…などは、実際には int クラス、float クラス、str クラスであり、それらの中では個別の変数(インスタンス)がどのような値になるかには関係なく、同じ型であれば共通して持っている機能が定義されています。 5 や 0.3 や 'Chainer' などは、それぞれ int クラスのインスタンス、float クラスのインスタンス、str クラスのインスタンスです

継承

class Link:
    def __init__(self):
        self.a = 1
        self.b = 2
l = Link()
l.a
1
l.b
2

このクラスを継承する、Chain というクラスを定義してみます。 継承を行う場合は、クラス定義の際にクラス名に続けて () を書き、その中にベースにしたいクラスの名前を書きます。 () の中に書かれたクラスのことを、定義されるクラスの親クラスといいます。 それに対し、() の中に書かれたクラスからみると、定義されるクラスは子クラスと呼ばれます。 親から子へ機能が受け継がれるためです。

class Chain(Link):
    def sum(self):
        return self.a + self.b
c = Chain()
c.a
1
c.b
2
c.sum()
3
class Chain(Link):

    def __init__(self):
        self.c = 5

    def sum(self):
        return self.a + self.b + self.c


c = Chain()
c.sum()
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-114-92631ebf55a8> in <module>
----> 1 c.sum()


<ipython-input-112-fc5a883e4286> in sum(self)
      5 
      6     def sum(self):
----> 7         return self.a + self.b + self.c
      8 
      9 


AttributeError: 'Chain' object has no attribute 'a'

'Chain' というオブジェクトは、'a' という名前の属性を持っていない、と言われています。 a という属性は、Chain の親クラスである Link の init() メソッドで定義されています。 そのため、Chain クラスをインスタンス化する際に、親クラスである Link の init() メソッドが呼ばれているのであれば、このエラーは起こらないはずです。 なぜエラーとなってしまったのでしょうか。

それは、Chain クラスにも init() メソッドを定義したため、親クラスである Link の init() メソッドが上書きされてしまい、実行され>なかったためです。 しかし、親クラスの init() メソッドを明示的に呼ぶことで、これは解決できます.
それには、super() という組み込み関数を用います。 これを用いると、子クラスから親クラスを参照することができます。

class Chain(Link):

    def __init__(self):
        # 親クラスの `__init__()` メソッドを呼び出す
        super().__init__()
        self.c = 5

    def sum(self):
        return self.a + self.b + self.c


c = Chain()
print('a, b, c = [{}, {}, {}]'.format(c.a, c.b, c.c))
print(c.sum())
a, b, c = [1, 2, 5]
8
# あるクラスを継承して作られたクラスを、さらに継承して別のクラスを定義することもできます。

class MyNetwork(Chain):

    def mul(self):
        return self.a * self.b * self.c
net = MyNetwork()
print('a, b, c = [{}, {}, {}]'.format(net.a, net.b, net.c))
print(net.mul())
a, b, c = [1, 2, 5]
10
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1