- 投稿日:2020-05-15T23:51:32+09:00
SwiftでN次元行列演算ライブラリを作ってみた
はじめに
初投稿です.
タイトルの通りなのですが,SwiftでN次元行列演算ライブラリMatftを作ってみました.(Mat rix演算をする・Swi ftで,の略でMatftです笑)事の発端は,会社の先輩の「Swiftで3行3列の逆行列を求めるコードを書いてほしい」という一言でした.
SwiftにはPythonのNumpyのようなN次元行列演算ライブラリがあるだろうと思ったのですが,調べると意外にもないんですよね...
公式のAccelerateは使い勝手悪そうだし,有名らしいsurgeも2次元まで?みたいでした.そんなこんなで,せっかくなので自作のN次元行列演算ライブラリを作ってみようと思いました.(3行3列の逆行列を求めるコードに対して,完全にオーバースペックですが笑)さらにそんなこんなで,Matftができました.そしてせっかくなので共有してみようということで,現在に至ります.
概要
基本的にはPythonのNumpyにならって作成したので,関数名や使い方はNumpyとほぼ同じです.
宣言
宣言は
ndarray
なるMfArray
で多次元配列を生成します.let a = MfArray([[[ -8, -7, -6, -5], [ -4, -3, -2, -1]], [[ 0, 1, 2, 3], [ 4, 5, 6, 7]]]) print(a) /* mfarray = [[[ -8.0, -7.0, -6.0, -5.0], [ -4.0, -3.0, -2.0, -1.0]], [[ 0.0, 1.0, 2.0, 3.0], [ 4.0, 5.0, 6.0, 7.0]]], type=Float, shape=[2, 2, 4] */型
いろいろな型に対応させたかったので,それなりの型を用意しました.
dtype
ならぬMfType
です.let a = MfArray([[[ -8, -7, -6, -5], [ -4, -3, -2, -1]], [[ 0, 1, 2, 3], [ 4, 5, 6, 7]]], mftype: .Float) print(a) /* mfarray = [[[ -8.0, -7.0, -6.0, -5.0], [ -4.0, -3.0, -2.0, -1.0]], [[ 0.0, 1.0, 2.0, 3.0], [ 4.0, 5.0, 6.0, 7.0]]], type=Float, shape=[2, 2, 4] */ let aa = MfArray([[[ -8, -7, -6, -5], [ -4, -3, -2, -1]], [[ 0, 1, 2, 3], [ 4, 5, 6, 7]]], mftype: .UInt) print(aa) /* mfarray = [[[ 4294967288, 4294967289, 4294967290, 4294967291], [ 4294967292, 4294967293, 4294967294, 4294967295]], [[ 0, 1, 2, 3], [ 4, 5, 6, 7]]], type=UInt, shape=[2, 2, 4] */ //Above output is same as numpy! /* >>> np.arange(-8, 8, dtype=np.uint32).reshape(2,2,4) array([[[4294967288, 4294967289, 4294967290, 4294967291], [4294967292, 4294967293, 4294967294, 4294967295]], [[ 0, 1, 2, 3], [ 4, 5, 6, 7]]], dtype=uint32)型一覧は以下のEnum型で定義しました.
※実を言うと,裏ではFloat
かDouble
で保存しているので,UInt
なんかは値が大きいとオーバーフローします.ただ,実用上は問題ないと思います.public enum MfType: Int{ case None // Unsupportted case Bool case UInt8 case UInt16 case UInt32 case UInt64 case UInt case Int8 case Int16 case Int32 case Int64 case Int case Float case Double case Object // Unsupported }Indexing
Numpyでいう
a[:, ::-1]
のようなスライスも~
で実装しました.
-1のような負のインデックスも実装しました.(これが一番苦労したかもしれません...)let a = Matft.mfarray.arange(start: 0, to: 27, by: 1, shape: [3,3,3]) print(a) /* mfarray = [[[ 0, 1, 2], [ 3, 4, 5], [ 6, 7, 8]], [[ 9, 10, 11], [ 12, 13, 14], [ 15, 16, 17]], [[ 18, 19, 20], [ 21, 22, 23], [ 24, 25, 26]]], type=Int, shape=[3, 3, 3] */ print(a[2,1,0]) // 21 print(a[1~3]) //same as a[1:3] for numpy /* mfarray = [[[ 9, 10, 11], [ 12, 13, 14], [ 15, 16, 17]], [[ 18, 19, 20], [ 21, 22, 23], [ 24, 25, 26]]], type=Int, shape=[2, 3, 3] */ print(a[-1~-3]) /* mfarray = [], type=Int, shape=[0, 3, 3] */ print(a[~~-1]) /* mfarray = [[[ 18, 19, 20], [ 21, 22, 23], [ 24, 25, 26]], [[ 9, 10, 11], [ 12, 13, 14], [ 15, 16, 17]], [[ 0, 1, 2], [ 3, 4, 5], [ 6, 7, 8]]], type=Int, shape=[3, 3, 3]*/その他関数一覧
ここからは,具体的な関数一覧です.主な計算はAccelerateに任せているので,計算時間はある程度担保されていると思います.
- 生成系
Matft Numpy Matft.mfarray.shallowcopy numpy.copy Matft.mfarray.deepcopy copy.deepcopy Matft.mfarray.nums numpy.ones * N Matft.mfarray.arange numpy.arange Matft.mfarray.eye numpy.eye Matft.mfarray.diag numpy.diag Matft.mfarray.vstack numpy.vstack Matft.mfarray.hstack numpy.hstack Matft.mfarray.concatenate numpy.concatenate
- 変換系
Matft Numpy Matft.mfarray.astype numpy.astype Matft.mfarray.transpose numpy.transpose Matft.mfarray.expand_dims numpy.expand_dims Matft.mfarray.squeeze numpy.squeeze Matft.mfarray.broadcast_to numpy.broadcast_to Matft.mfarray.conv_order numpy.ascontiguousarray Matft.mfarray.flatten numpy.flatten Matft.mfarray.flip numpy.flip Matft.mfarray.swapaxes numpy.swapaxes Matft.mfarray.moveaxis numpy.moveaxis Matft.mfarray.sort numpy.sort Matft.mfarray.argsort numpy.argsort
- ファイル関係 saveが未完成です.
Matft Numpy Matft.mfarray.file.loadtxt numpy.loadtxt Matft.mfarray.file.genfromtxt numpy.genfromtxt
- 演算系
Matft Numpy Matft.mfarray.add numpy.add Matft.mfarray.sub numpy.sub Matft.mfarray.div numpy.div Matft.mfarray.mul numpy.multiply Matft.mfarray.inner numpy.inner Matft.mfarray.cross numpy.cross Matft.mfarray.equal numpy.equal Matft.mfarray.allEqual numpy.array_equal Matft.mfarray.neg numpy.negative
- 初等関数系
Matft Numpy Matft.mfarray.math.sin numpy.sin Matft.mfarray.math.asin numpy.asin Matft.mfarray.math.sinh numpy.sinh Matft.mfarray.math.asinh numpy.asinh Matft.mfarray.math.sin numpy.cos Matft.mfarray.math.acos numpy.acos Matft.mfarray.math.cosh numpy.cosh Matft.mfarray.math.acosh numpy.acosh Matft.mfarray.math.tan numpy.tan Matft.mfarray.math.atan numpy.atan Matft.mfarray.math.tanh numpy.tanh Matft.mfarray.math.atanh numpy.atanh 面倒なので,省略します...笑
ここを見てください
- 高階関数系
Matft Numpy Matft.mfarray.stats.mean numpy.mean Matft.mfarray.math.max numpy.max Matft.mfarray.math.argmax numpy.argmax Matft.mfarray.math.min numpy.min Matft.mfarray.math.argmin numpy.argmin Matft.mfarray.math.sum numpy.sum
- 線形代数系
Matft Numpy Matft.mfarray.linalg.solve numpy.linalg.solve Matft.mfarray.linalg.inv numpy.linalg.inv Matft.mfarray.linalg.det numpy.linalg.det Matft.mfarray.linalg.eigen numpy.linalg.eig Matft.mfarray.linalg.svd numpy.linalg.svd Matft.mfarray.linalg.polar_left scipy.linalg.polar Matft.mfarray.linalg.polar_right scipy.linalg.polar インストール
SwiftPMとCocoaPodに対応しました.
SwiftPM
- Import
- アップデート
CocoaPods
- Podfile作成 (すでにある場合は無視)
pod init
pod 'Matft'
をPodfileに追記target 'your project' do pod 'Matft' end
- インストール
pod install
Performance
Accelerateに任せているので,計算は担保されていると言いましたが,足し算だけ速度を計算してみました.
時間があれば他の関数も調べます...
case Matft Numpy 1 1.14ms 962 µs 2 4.20ms 5.68 ms 3 4.17ms 3.92 ms
- Matft
func testPefAdd1() { do{ let a = Matft.mfarray.arange(start: 0, to: 10*10*10*10*10*10, by: 1, shape: [10,10,10,10,10,10]) let b = Matft.mfarray.arange(start: 0, to: -10*10*10*10*10*10, by: -1, shape: [10,10,10,10,10,10]) self.measure { let _ = a+b } /* '-[MatftTests.ArithmeticPefTests testPefAdd1]' measured [Time, seconds] average: 0.001, relative standard deviation: 23.418%, values: [0.001707, 0.001141, 0.000999, 0.000969, 0.001029, 0.000979, 0.001031, 0.000986, 0.000963, 0.001631] 1.14ms */ } } func testPefAdd2(){ do{ let a = Matft.mfarray.arange(start: 0, to: 10*10*10*10*10*10, by: 1, shape: [10,10,10,10,10,10]) let b = a.transpose(axes: [0,3,4,2,1,5]) let c = a.T self.measure { let _ = b+c } /* '-[MatftTests.ArithmeticPefTests testPefAdd2]' measured [Time, seconds] average: 0.004, relative standard deviation: 5.842%, values: [0.004680, 0.003993, 0.004159, 0.004564, 0.003955, 0.004200, 0.003998, 0.004317, 0.003919, 0.004248] 4.20ms */ } } func testPefAdd3(){ do{ let a = Matft.mfarray.arange(start: 0, to: 10*10*10*10*10*10, by: 1, shape: [10,10,10,10,10,10]) let b = a.transpose(axes: [1,2,3,4,5,0]) let c = a.T self.measure { let _ = b+c } /* '-[MatftTests.ArithmeticPefTests testPefAdd3]' measured [Time, seconds] average: 0.004, relative standard deviation: 16.815%, values: [0.004906, 0.003785, 0.003702, 0.005981, 0.004261, 0.003665, 0.004083, 0.003654, 0.003836, 0.003874] 4.17ms */ } }
- Numpy
In [1]: import numpy as np #import timeit a = np.arange(10**6).reshape((10,10,10,10,10,10)) b = np.arange(0, -10**6, -1).reshape((10,10,10,10,10,10)) #timeit.timeit("b+c", repeat=10, globals=globals()) %timeit -n 10 a+b 962 µs ± 273 µs per loop (mean ± std. dev. of 7 runs, 10 loops each) In [2]: a = np.arange(10**6).reshape((10,10,10,10,10,10)) b = a.transpose((0,3,4,2,1,5)) c = a.T #timeit.timeit("b+c", repeat=10, globals=globals()) %timeit -n 10 b+c 5.68 ms ± 1.45 ms per loop (mean ± std. dev. of 7 runs, 10 loops each) In [3]: a = np.arange(10**6).reshape((10,10,10,10,10,10)) b = a.transpose((1,2,3,4,5,0)) c = a.T #timeit.timeit("b+c", repeat=10, globals=globals()) %timeit -n 10 b+c 3.92 ms ± 897 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)個人的に気に入っているところ
- 名前
- 複数型対応
- Numpyぽさ
最後に
気まぐれで作成しましたが,思ったよりいい感じのものができました.ただただ僕の検索力不足なだけで,より良いライブラリがあるかもしれませんが,是非試していただけると嬉しいです.(環境依存のチェックができていませんので,是非お願いします...)
とにもかくにもいい勉強になりました.ありがとうございました.参考
numpy
scipy
Accelerate
SwiftでNDArray書く(テストケースを参考にさせていただきました.)
- 投稿日:2020-05-15T23:51:32+09:00
SwiftでN次元行列演算ライブラリMatftを作ってみた
はじめに
初投稿です.
タイトルの通りなのですが,SwiftでN次元行列演算ライブラリMatftを作ってみました.(Mat rix演算をする・Swi ftで,の略でMatftです笑)事の発端は,会社の先輩の「Swiftで3行3列の逆行列を求めるコードを書いてほしい」という一言でした.
SwiftにはPythonのNumpyのようなN次元行列演算ライブラリがあるだろうと思ったのですが,調べると意外にもないんですよね...
公式のAccelerateは使い勝手悪そうだし,有名らしいsurgeも2次元まで?みたいでした.そんなこんなで,せっかくなので自作のN次元行列演算ライブラリを作ってみようと思いました.(3行3列の逆行列を求めるコードに対して,完全にオーバースペックですが笑)さらにそんなこんなで,Matftができました.そしてせっかくなので共有してみようということで,現在に至ります.
概要
基本的にはPythonのNumpyにならって作成したので,関数名や使い方はNumpyとほぼ同じです.
宣言
宣言は
ndarray
なるMfArray
で多次元配列を生成します.let a = MfArray([[[ -8, -7, -6, -5], [ -4, -3, -2, -1]], [[ 0, 1, 2, 3], [ 4, 5, 6, 7]]]) print(a) /* mfarray = [[[ -8.0, -7.0, -6.0, -5.0], [ -4.0, -3.0, -2.0, -1.0]], [[ 0.0, 1.0, 2.0, 3.0], [ 4.0, 5.0, 6.0, 7.0]]], type=Float, shape=[2, 2, 4] */型
いろいろな型に対応させたかったので,それなりの型を用意しました.
dtype
ならぬMfType
です.let a = MfArray([[[ -8, -7, -6, -5], [ -4, -3, -2, -1]], [[ 0, 1, 2, 3], [ 4, 5, 6, 7]]], mftype: .Float) print(a) /* mfarray = [[[ -8.0, -7.0, -6.0, -5.0], [ -4.0, -3.0, -2.0, -1.0]], [[ 0.0, 1.0, 2.0, 3.0], [ 4.0, 5.0, 6.0, 7.0]]], type=Float, shape=[2, 2, 4] */ let aa = MfArray([[[ -8, -7, -6, -5], [ -4, -3, -2, -1]], [[ 0, 1, 2, 3], [ 4, 5, 6, 7]]], mftype: .UInt) print(aa) /* mfarray = [[[ 4294967288, 4294967289, 4294967290, 4294967291], [ 4294967292, 4294967293, 4294967294, 4294967295]], [[ 0, 1, 2, 3], [ 4, 5, 6, 7]]], type=UInt, shape=[2, 2, 4] */ //Above output is same as numpy! /* >>> np.arange(-8, 8, dtype=np.uint32).reshape(2,2,4) array([[[4294967288, 4294967289, 4294967290, 4294967291], [4294967292, 4294967293, 4294967294, 4294967295]], [[ 0, 1, 2, 3], [ 4, 5, 6, 7]]], dtype=uint32)型一覧は以下のEnum型で定義しました.
※実を言うと,裏ではFloat
かDouble
で保存しているので,UInt
なんかは値が大きいとオーバーフローします.ただ,実用上は問題ないと思います.public enum MfType: Int{ case None // Unsupportted case Bool case UInt8 case UInt16 case UInt32 case UInt64 case UInt case Int8 case Int16 case Int32 case Int64 case Int case Float case Double case Object // Unsupported }Indexing
Numpyでいう
a[:, ::-1]
のようなスライスも~
で実装しました.
-1のような負のインデックスも実装しました.(これが一番苦労したかもしれません...)let a = Matft.mfarray.arange(start: 0, to: 27, by: 1, shape: [3,3,3]) print(a) /* mfarray = [[[ 0, 1, 2], [ 3, 4, 5], [ 6, 7, 8]], [[ 9, 10, 11], [ 12, 13, 14], [ 15, 16, 17]], [[ 18, 19, 20], [ 21, 22, 23], [ 24, 25, 26]]], type=Int, shape=[3, 3, 3] */ print(a[2,1,0]) // 21 print(a[1~3]) //same as a[1:3] for numpy /* mfarray = [[[ 9, 10, 11], [ 12, 13, 14], [ 15, 16, 17]], [[ 18, 19, 20], [ 21, 22, 23], [ 24, 25, 26]]], type=Int, shape=[2, 3, 3] */ print(a[-1~-3]) /* mfarray = [], type=Int, shape=[0, 3, 3] */ print(a[~~-1]) /* mfarray = [[[ 18, 19, 20], [ 21, 22, 23], [ 24, 25, 26]], [[ 9, 10, 11], [ 12, 13, 14], [ 15, 16, 17]], [[ 0, 1, 2], [ 3, 4, 5], [ 6, 7, 8]]], type=Int, shape=[3, 3, 3]*/その他関数一覧
ここからは,具体的な関数一覧です.主な計算はAccelerateに任せているので,計算時間はある程度担保されていると思います.
* はmethodも存在することを意味します.つまり,
a
がMfArray
であれば,a.shallowcopy()
が使えます.
- 生成系
Matft Numpy *Matft.mfarray.shallowcopy *numpy.copy *Matft.mfarray.deepcopy copy.deepcopy Matft.mfarray.nums numpy.ones * N Matft.mfarray.arange numpy.arange Matft.mfarray.eye numpy.eye Matft.mfarray.diag numpy.diag Matft.mfarray.vstack numpy.vstack Matft.mfarray.hstack numpy.hstack Matft.mfarray.concatenate numpy.concatenate
- 変換系
Matft Numpy *Matft.mfarray.astype *numpy.astype *Matft.mfarray.transpose *numpy.transpose *Matft.mfarray.expand_dims *numpy.expand_dims *Matft.mfarray.squeeze *numpy.squeeze *Matft.mfarray.broadcast_to *numpy.broadcast_to *Matft.mfarray.conv_order *numpy.ascontiguousarray *Matft.mfarray.flatten *numpy.flatten *Matft.mfarray.flip *numpy.flip *Matft.mfarray.swapaxes *numpy.swapaxes *Matft.mfarray.moveaxis *numpy.moveaxis *Matft.mfarray.sort *numpy.sort *Matft.mfarray.argsort *numpy.argsort
- ファイル関係 saveが未完成です.
Matft Numpy Matft.mfarray.file.loadtxt numpy.loadtxt Matft.mfarray.file.genfromtxt numpy.genfromtxt
- 演算系
2行目が演算子です.
Matft Numpy Matft.mfarray.add
+numpy.add
+Matft.mfarray.sub
-numpy.sub
-Matft.mfarray.div
/numpy.div
.Matft.mfarray.mul
*numpy.multiply
*Matft.mfarray.inner
*+numpy.inner
n/aMatft.mfarray.cross
*^numpy.cross
n/aMatft.mfarray.matmul
*&numpy.matmul
@Matft.mfarray.equal
===numpy.equal
==Matft.mfarray.allEqual
==numpy.array_equal
n/aMatft.mfarray.neg
-numpy.negative
-
- 初等関数系
Matft Numpy Matft.mfarray.math.sin numpy.sin Matft.mfarray.math.asin numpy.asin Matft.mfarray.math.sinh numpy.sinh Matft.mfarray.math.asinh numpy.asinh Matft.mfarray.math.sin numpy.cos Matft.mfarray.math.acos numpy.acos Matft.mfarray.math.cosh numpy.cosh Matft.mfarray.math.acosh numpy.acosh Matft.mfarray.math.tan numpy.tan Matft.mfarray.math.atan numpy.atan Matft.mfarray.math.tanh numpy.tanh Matft.mfarray.math.atanh numpy.atanh 面倒なので,省略します...笑
ここを見てください
- 高階関数系
Matft Numpy *Matft.mfarray.stats.mean *numpy.mean *Matft.mfarray.stats.max *numpy.max *Matft.mfarray.stats.argmax *numpy.argmax *Matft.mfarray.stats.min *numpy.min *Matft.mfarray.stats.argmin *numpy.argmin *Matft.mfarray.stats.sum *numpy.sum
- 線形代数系
Matft Numpy Matft.mfarray.linalg.solve numpy.linalg.solve Matft.mfarray.linalg.inv numpy.linalg.inv Matft.mfarray.linalg.det numpy.linalg.det Matft.mfarray.linalg.eigen numpy.linalg.eig Matft.mfarray.linalg.svd numpy.linalg.svd Matft.mfarray.linalg.polar_left scipy.linalg.polar Matft.mfarray.linalg.polar_right scipy.linalg.polar インストール
SwiftPMとCocoaPodに対応しました.
SwiftPM
- Import
- アップデート
CocoaPods
- Podfile作成 (すでにある場合は無視)
pod init
pod 'Matft'
をPodfileに追記target 'your project' do pod 'Matft' end
- インストール
pod install
Performance
Accelerateに任せているので,計算は担保されていると言いましたが,足し算だけ速度を計算してみました.
時間があれば他の関数も調べます...
case Matft Numpy 1 1.14ms 962 µs 2 4.20ms 5.68 ms 3 4.17ms 3.92 ms
- Matft
func testPefAdd1() { do{ let a = Matft.mfarray.arange(start: 0, to: 10*10*10*10*10*10, by: 1, shape: [10,10,10,10,10,10]) let b = Matft.mfarray.arange(start: 0, to: -10*10*10*10*10*10, by: -1, shape: [10,10,10,10,10,10]) self.measure { let _ = a+b } /* '-[MatftTests.ArithmeticPefTests testPefAdd1]' measured [Time, seconds] average: 0.001, relative standard deviation: 23.418%, values: [0.001707, 0.001141, 0.000999, 0.000969, 0.001029, 0.000979, 0.001031, 0.000986, 0.000963, 0.001631] 1.14ms */ } } func testPefAdd2(){ do{ let a = Matft.mfarray.arange(start: 0, to: 10*10*10*10*10*10, by: 1, shape: [10,10,10,10,10,10]) let b = a.transpose(axes: [0,3,4,2,1,5]) let c = a.T self.measure { let _ = b+c } /* '-[MatftTests.ArithmeticPefTests testPefAdd2]' measured [Time, seconds] average: 0.004, relative standard deviation: 5.842%, values: [0.004680, 0.003993, 0.004159, 0.004564, 0.003955, 0.004200, 0.003998, 0.004317, 0.003919, 0.004248] 4.20ms */ } } func testPefAdd3(){ do{ let a = Matft.mfarray.arange(start: 0, to: 10*10*10*10*10*10, by: 1, shape: [10,10,10,10,10,10]) let b = a.transpose(axes: [1,2,3,4,5,0]) let c = a.T self.measure { let _ = b+c } /* '-[MatftTests.ArithmeticPefTests testPefAdd3]' measured [Time, seconds] average: 0.004, relative standard deviation: 16.815%, values: [0.004906, 0.003785, 0.003702, 0.005981, 0.004261, 0.003665, 0.004083, 0.003654, 0.003836, 0.003874] 4.17ms */ } }
- Numpy
In [1]: import numpy as np #import timeit a = np.arange(10**6).reshape((10,10,10,10,10,10)) b = np.arange(0, -10**6, -1).reshape((10,10,10,10,10,10)) #timeit.timeit("b+c", repeat=10, globals=globals()) %timeit -n 10 a+b 962 µs ± 273 µs per loop (mean ± std. dev. of 7 runs, 10 loops each) In [2]: a = np.arange(10**6).reshape((10,10,10,10,10,10)) b = a.transpose((0,3,4,2,1,5)) c = a.T #timeit.timeit("b+c", repeat=10, globals=globals()) %timeit -n 10 b+c 5.68 ms ± 1.45 ms per loop (mean ± std. dev. of 7 runs, 10 loops each) In [3]: a = np.arange(10**6).reshape((10,10,10,10,10,10)) b = a.transpose((1,2,3,4,5,0)) c = a.T #timeit.timeit("b+c", repeat=10, globals=globals()) %timeit -n 10 b+c 3.92 ms ± 897 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)個人的に気に入っているところ
微妙なところ
- まだまだオーバーヘッドが存在する
- せっかくMfArrayのOrderを保持する
flag
を実装したのに,無駄なコピーをしている箇所がいくつかある- 生成系が
Array
→MfArray
に変換している(vDSPとかを使いたい)- Protocolがうまく使えていない
最後に
気まぐれで作成しましたが,思ったよりいい感じのものができました.ただただ僕の検索力不足なだけで,より良いライブラリがあるかもしれませんが,是非試していただけると嬉しいです.(環境依存のチェックができていませんので,是非お願いします...)
とにもかくにもいい勉強になりました.ありがとうございました.参考
numpy
scipy
Accelerate
SwiftでNDArray書く(テストケースを参考にさせていただきました.)
- 投稿日:2020-05-15T18:21:11+09:00
iOSの購読管理画面の呼び出し方
メモな感じですいません。
iOSの購読管理画面を呼び出す方法、ここに書いてあります。
https://developer.apple.com/jp/documentation/storekit/in-app_purchase/let url = "https://apps.apple.com/account/subscriptions" UIApplication.shared.open(URL(string: url)!, options: [:], completionHandler: nil)WWDCのビデオはこちらです。
https://developer.apple.com/videos/play/wwdc2018/705/
- 投稿日:2020-05-15T15:38:42+09:00
SF SymbolsをiOS12でも使用できるようにする
SF Symbolsとは
iOS12でも使えるように
環境
Xcode 11.4.1
準備
SF Symbolsアプリをダウンロードする
https://developer.apple.com/design/human-interface-guidelines/sf-symbols/overview/XcodeのAssets内で『+』を押して『New Symbol Image Asset』選択して
SF Symbolsアプリから出力したSVGファイルをインポートすることで準備完了
あとはStoryBoard、コードでセットするだけで使用できます
使用する
Sample.swiftlet image = UIImage(name:"Pin")
- 投稿日:2020-05-15T15:30:01+09:00
【Swift】アルバム起動
①photosをインポートする
import Photos② UIImagePickerControllerDelegate,UINavigationControllerDelegate を継承する。
UIImagePickerControllerDelegate,UINavigationControllerDelegate③以下のコードをメソッドとして記述する。
//アルバムを起動 func doAlbum(){ let sourceType:UIImagePickerController.SourceType = .photoLibrary //カメラが利用可能かチェックする if UIImagePickerController.isSourceTypeAvailable(.photoLibrary){ let cameraPicker = UIImagePickerController() cameraPicker.sourceType = sourceType cameraPicker.delegate = self cameraPicker.allowsEditing = true present(cameraPicker, animated: true, completion: nil) } }④アルバムから画像が選択されたときの呼ばれる箇所。以下のコードを記述する。
//カメラ撮影orアルバムから画像選択された時に呼ばれる func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { if info[.originalImage] as? UIImage != nil{ let selectedImage = info[.originalImage] as! UIImage UserDefaults.standard.set(selectedImage.jpegData(compressionQuality: 0.1), forKey: "userImage") logoImageView.image = selectedImage picker.dismiss(animated: true, completion: nil) } }※理解のため、自分のわかりやすい単語に置き換えてあるので、表記に問題ありの場合が多いかと思いますが、ご了承ください。。
- 投稿日:2020-05-15T14:10:10+09:00
NSView alphaValueが効かない時に
NSView.alphaValue
を設定するとViewが全く見えなくなるバグに遭遇したので、解決方の備忘録。
alphaValue
は内部的にCALayer
を使っているようで、場合によってはCALayer
なしでもうまく行くが、場合によってはうまくいかないようなのでview.wantsLayer = trueで解決した。
- 投稿日:2020-05-15T10:52:45+09:00
【Swift】pinch in or out ができない時はisExclusiveTouchがtrueになっているかも!
pinch in or out ができない時はisExclusiveTouchがtrueになっているかも!
UIScrollViewのZoom周りをちゃんと設定したが、うまく拡大、縮小できないときは
isExclusiveTouch = true
になっていないか確認しましょう。UIView.appearance().isExclusiveTouch = true UIButton.appearance().isExclusiveTouch = true self.isExclusiveTouch = true上記のような設定がUIScrollView上のViewやCellに設定されていると、複数のViewで同時にTouchイベントを処理しなくなります。
なので、pinch in, outのような複数の指で行う動作がうまく行かないときがあります。
- 投稿日:2020-05-15T10:38:45+09:00
swiftのシーケンスとコレクションを扱うためのプロトコル 超初心者のメモ
シーケンスとコレクションを扱うためのプロトコル
シーケンスとはその要素に一方向から順次アクセス可能なデータ構造です。
例えば、配列は先頭のインデックスから要素に順次アクセスできるため、シーケンスの一種であると言えます。
標準ライブラリにはシーケンスを汎用的に扱うためにSequenceプロトコルが用意されています。コレクションは、一方向からの順次アクセスと、特定のインデックスの値への直接アクセスが可能なデータ構造です。
機能の差からわかるように、コレクションはシーケンスを内包する概念です。Sequenceプロトコルは、次のインターフェースを提供します。
forEach
filter
map
flatMap
compactMap
reduce使い方の例
filter
指定した条件を満たす要素のみを含む、新しいシーケンスを返すメソッドです。let array = [1, 2, 3, 4, 5] let enumerated2 = array.filter({e in e % 2 == 0}) print(enumerated2) // 2, 4compactMap
compactMapは全ての要素を特定の処理で変換するという点ではmapメソッドと同じですが、変換できない値を無視するという点が異なります。let strings = ["foo", "123", "bar", "456"] let integers = strings.compactMap({value in Int(value)}) print(integers)結果は、変換に成功した123と456のみを含むInt型の配列です。変換に失敗した"foo"や"bar"は無視された事がわかります。
Collectionプロトコルは、Sequenceプロトコルを継承しています。
そのため、Collectionプロトコルにい準拠する型に対しては、Sequenceプロトコルが提供するfilterや、mapを使用可能です。let array = [1, 2, 3, 4, 5] array[3] // 4 array.isEmpty // false array.count // 5 array.first // 1 array.last // 5
- 投稿日:2020-05-15T09:00:59+09:00
UIalertControllerが微妙に使いづらかったのでいい感じのAlertControllerを自作する!
はじめに
先日インターン先のタスクで共通で使う通知部分を実装する際、デフォルトのUIAlertControllerだとボタンのテキストをいじれなかったりとデザインに合わせることが難しかったので、一からUialertControllerに似た実装の仕方で作ってみたのでそれについてまとめてみようと思います!
github
https://github.com/ikemoti/OriginalUIAlertController
デザインの用件内容
・アラートのttitleやmessage部分のフォントやtextColorを変えたい
(デフォルトのアラートだと変更不可能)
・アラートの上部にバーナー画像を任意で追加可能にして欲しい
・ボタンの数を任意で選択できるようにしてフォントや文字色も選択可能にして欲しい
・なるべく使う際にUIAlerttControllerに似た使い方ができるようにして欲しい完成図
①アニメーション部分
はじめに土台となるアラートのアニメーション部分を作成しました!
apublic class DialogAnimation: NSObject, UIViewControllerAnimatedTransitioning { // true: dismiss // false: present private let isPresenting: Bool init(isPresenting: Bool) { self.isPresenting = isPresenting } public func transitionDuration(using _: UIViewControllerContextTransitioning?) -> TimeInterval { return 0.20 } public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { if isPresenting { dismissAnimation(transitionContext) } else { presentAnimation(transitionContext) } } private func presentAnimation(_ transitionContext: UIViewControllerContextTransitioning) { guard let alert = transitionContext .viewController(forKey: UITransitionContextViewControllerKey.to) as? DialogController else { fatalError("ViewController is not defined sucessfully") } let container = transitionContext.containerView alert.backgroundView.alpha = 0 alert.contentView.alpha = 0 alert.contentView.transform = CGAffineTransform(scaleX: 0.9, y: 0.9) container.addSubview(alert.view) UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: { alert.backgroundView.alpha = 1 alert.contentView.alpha = 1 alert.contentView.transform = .identity }, completion: { _ in transitionContext.completeTransition(true) }) } //DissMissAnimaitonb部分は省略ポイントとして
ただ単にViewを表示するだけだと,遷移前の画面が投下されないので表示されるViewをbackgroundViewとAlertViewに分割してbackgroundViewを透過させることで無事表示できるようになりました②Controller部分について
a//Controller部分 public class DialogController: UIViewController, UIViewControllerTransitioningDelegate { //DialogControllerのベースとなるView private(set) lazy var backgroundView: UIView = { let view = UIView() view.backgroundColor = UIColor.black.withAlphaComponent(0.15) view.isOpaque = false view.translatesAutoresizingMaskIntoConstraints = false return view }() let contentView: UIView = .init() let contentStackView: UIStackView = .init() private let bannerView: UIImageView = .init() private var bannerAspectRatioConstraint: NSLayoutConstraint? private let textContainer: UIView = UIView() private let textStackView: UIStackView = UIStackView() private lazy var textStackViewTopAnchor: NSLayoutConstraint = { return textStackView.topAnchor.constraint(equalTo: textContainer.topAnchor, constant: 40) }() private lazy var textStackViewBottomAnchor: NSLayoutConstraint = { return textStackView.bottomAnchor.constraint(equalTo: textContainer.bottomAnchor, constant: -40) }() private let titleLabel: UILabel = .init() private let messageLabel: UILabel = .init() private let buttonStackview: UIStackView = .init() private let topBorder: UIView = .init() private var actions = [DialogAction]() override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) providesPresentationContextTransitionStyle = true definesPresentationContext = true modalPresentationStyle = UIModalPresentationStyle.custom transitioningDelegate = self } //簡易イニシャライザ //引数がnilかどうかでcontentViewが可変する public convenience init(title: String, message: String?, banner: UIImage?) { self.init(nibName: nil, bundle: nil) titleLabel.text = title messageLabel.isHidden = message == nil messageLabel.text = message bannerView.image = banner bannerView.isHidden = banner == nil textStackViewTopAnchor.constant = banner == nil ? 40 : 24 textStackViewBottomAnchor.constant = banner == nil ? -40 : -24 if let banner = banner { bannerAspectRatioConstraint = bannerView.heightAnchor.constraint(equalTo: bannerView.widthAnchor, multiplier: banner.size.height / banner.size.width) } } required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } public override func viewDidLoad() { super.viewDidLoad() setAttrributes() constructViews() setLayout() } @objc private func buttonEvent(sender: UIButton) { self.dismiss(animated: true, completion: nil) } public func animationController(forDismissed _: UIViewController) -> UIViewControllerAnimatedTransitioning? { return DialogAnimation(isPresenting: true) } //ボタン追加時のメゾット、二つ目のボタン追加時からボタンの追加前にborderを設置する public func add(action: DialogAction) { let button = UIButton() button.setTitle(action.title, for: UIControl.State()) button.setTitleColor(action.titleColor, for: .normal) button.titleLabel?.font = action.titleFont button.heightAnchor.constraint(equalToConstant: 44).isActive = true button.addTarget(self, action: #selector(buttonEvent(sender:)), for: UIControl.Event.touchUpInside) button.translatesAutoresizingMaskIntoConstraints = false if actions.count > 0 { let border = UIView() border.translatesAutoresizingMaskIntoConstraints = false border.widthAnchor.constraint(equalToConstant: 0.5).isActive = true border.heightAnchor.constraint(equalToConstant: 44).isActive = true border.backgroundColor = UIColor.gray buttonStackview.addArrangedSubview(border) } buttonStackview.addArrangedSubview(button) if let alertView = buttonStackview.arrangedSubviews.first { button.widthAnchor.constraint(equalTo: alertView.widthAnchor ).isActive = true } actions.append(action) } public func animationController( forPresented _: UIViewController, presenting _: UIViewController, source _: UIViewController ) -> UIViewControllerAnimatedTransitioning? { return DialogAnimation(isPresenting: false) } }Controller部分に関して引数の有無に合わせてのcontentViewの変化するようにしたり、
ボタンの追加時に個数によってボーダーを追加するような実装にしました!③ボタンに関して
apublic class DialogAction { public enum ActionStyle { case `default` case cancel case destructive case custom(color: UIColor, font: UIFont) } private var handler: ((_ action: DialogAction) -> Void)? public private(set) var style: ActionStyle public private(set) var title: String public var titleColor: UIColor { switch self.style { case .default: return UIColor.black case .cancel: return UIColor.black case .destructive: return UIColor.red case let .custom(color, _): return color } } public var titleFont: UIFont { switch self.style { case .default: return UIFont.boldSystemFont(ofSize: 14) case .cancel: return UIFont.systemFont(ofSize: 14) case .destructive: return UIFont.boldSystemFont(ofSize: 14) case let .custom(_, font): return font } } public init(title: String, style: ActionStyle, handler: ((_ action: DialogAction) -> Void)?) { self.handler = handler self.style = style self.title = title } }ボタンに関しては通常はdefault、cancel、destructiveの三つですが今回自由にカスタムできるように作って欲しいとのことだったのでcustomを追加してフォントとテキストカラーを自由に選べるようにしました!
④完成時の実装例
aclass ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad()} @IBAction func `default`(_ sender: Any) { let alert = UIAlertController(title: "タイトル", message: "メッセージ", preferredStyle: .alert) let cancel = UIAlertAction(title: "キャンセル", style: .cancel,handler: nil) let ok = UIAlertAction(title: "OK", style: .default,handler: nil) alert.addAction(cancel) alert.addAction(ok) self.present(alert, animated: true, completion: nil) } @IBAction func Original(_ sender: Any) { let alert2 = DialogController(title: "タイトル", message:"メッセージ", banner:UIImage(imageLiteralResourceName:"Image" ) ) let cancel2 = DialogAction(title: "キャンセル", style: .custom(color: UIColor.purple, font: UIFont.systemFont(ofSize: 16)), handler:nil ) let ok2 = DialogAction(title: "Ok", style: .default,handler: nil) alert2.add(action: cancel2) alert2.add(action: ok2) self.present(alert2, animated: true, completion: nil) }今回無事に実装できたのですが、animation関連がの知識不足を感じたので、また勉強していきたいなと思いました!?♂️
- 投稿日:2020-05-15T08:59:50+09:00
こんなソースコードはイヤだ-どうしてもindexが欲しいのか
- 投稿日:2020-05-15T07:47:41+09:00
アニメ画像の昼/夜認識システムの作成:(1/3)「Create ML」を用いた機械学習モデルを作成
本シリーズの記事一覧:
- 1.(本記事)「Core ML」モデルを「Create ML」で既存のラベル付けされたアニメ画像を入力として用いてトレーニングする。
- 2. そのモデルと「Vision」フレームワークを用いて新規画像からラベルを取得する。
- 3. それをもとに時間帯に合わせてmacOSの壁紙を変更する
意図:
Mac OSの壁紙として、時刻に合わせてさまざまなアニメ画像を設定するアプリの開発。
機械学習を使用して、アニメ画像に自動的に昼または夜としてのマーク付けを行う。
アニメ画像から昼夜の状態を認識する必要があるため、機械学習を取り入れます。
このアプリは、例えば日中には昼の場面のさまざまなアニメ画像を壁紙として設定します。そして夜間には、夜の場面のさまざまなアニメ画像を壁紙に設定します。
こちらは、「Create ML」を用いて機械学習モデルを作成する方法を解説する記事の第1部となります。
早速始めましょう!
機械学習モデルのトレーニング
まずは機械学習モデルのトレーニングと機械学習モデルファイルの取得に取り掛かりましょう。
「Create ML」ツールを開く
「Xcode」に搭載されている「Create ML」は、「iOS」、「iPadOS」、および「Mac OS」で使える機械学習モデル(拡張子.mlmodelのファイル)の生成に利用できる便利なツールで、「Xcode」内にインストールされています。
開くには、画面上部のシステムバーにある「Xcode」をクリックします。
そして、表示されたメニューから、「Open Developer Tool(開発者ツールを開く)」を選択し、「Create ML」をクリックして開きます
開いた「Create ML」ツールの画面上部のシステムバーにある「File(ファイル)」をクリックし、その後「New Project(新規プロジェクト)」をクリックします
「Image Classifier(画像分類システム)」を選択します。画像を入力するとその画像のラベルを出力するモデルをトレーニングするということです。
次に、情報を入力して「Next(次)」をクリックします
「Create(作成)」をクリックしてモデルを作成します。
画像の収集
ここでは、プログラムに任意のアニメ画像の時間帯が昼か夜かを予測させることを目指しています。この機械学習モデルをトレーニングするには、2グループの画像、つまり昼の時間帯の画像と暗い(夜の)時間帯の画像を見つける必要があります。
そこで、「アニメ」という名前のフォルダーを作ります。そして、「アニメ」の中に2つのフォルダーを作り、名前は「昼」と「夜」にします。「アニメ」フォルダーの中にはそのほかには何も入れないでください。インターネットから何枚かアニメ画像を取得し、時間帯を判断して「昼」フォルダーか「夜」フォルダーにドラッグします。
用意する画像数が多ければ多いほど、モデルはより正確になります。
モデルのトレーニングの開始
次に、「Create ML」アプリに切り替えて、「Training Data(トレーニングデータ)」セクションを見つけ、「Choose(選択)」ドロップダウンメニューをクリックして「Select Files(ファイルを選択)」をクリックします
先ほど作成したフォルダーを選択して「Open(開く)」をクリックします
すると、黄色の矢印で、「Classes(クラス)」の総数、つまりカテゴリー数が指し示されて表示されます。青の矢印で用意した画像の総数が指し示されて表示されます。
「Augmentations(拡張)」の設定
実行ボタンをクリックしてトレーニングを開始します:
処理中
お茶でも楽しみながらトレーニングの完了を待ちます。
トレーニング完了
テスト用画像を何枚か用意してモデルをテストします
モデルのトレーニングが完了したので、どれほど正確なモデルになったかをテストしましょう。Testing(テスト)セクションに切り替えましょう。
次に、もう1つフォルダーを作り、名前を「Testing(テスト)」にします。その中に2つのフォルダーを作り、名前を「Day(昼)」と「Night(夜)」にします。新たにいくつかアニメの画像を取得して、それぞれのフォルダーに分類して保存します。
先ほどと同じように、「Choose(選択)」、「Select Files(ファイルを選択)」をクリックします。そしてこのフォルダーを選択します。
「Test Model(モデルをテスト)」をクリックします
この例では正確性は100%となりました!素晴らしい結果です!
お手元では正確性が低くても、心配することはありません。画像をさらに追加して再度トレーニングを開始してください。
直接テスト
画像をドラッグ・ドロップするだけでテストすることもできます。
「Output(出力)」をクリックし、左のパネルに画像をドラッグするだけで結果を確かめられます。
モデルの書き出し
「Output(出力)」にあるモデルファイルを見つけ、フォルダーにドラッグするだけです。
次の記事の内容
次の記事では、このモデルをコードから呼び出すことで、どのようにアニメ画像の時間帯の昼/夜の認識ができるかを解説します。