ホーム » TensorFlow 2.0 » TensorFlow 2.0 : Tutorials : データのロードと前処理 :- CSV データをロードする

TensorFlow 2.0 : Tutorials : データのロードと前処理 :- CSV データをロードする

TensorFlow 2.0 : Beginner Tutorials : データのロードと前処理 :- CSV データをロードする (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 10/06/2019

* 本ページは、TensorFlow org サイトの TF 2.0 – Beginner Tutorials – Load and preprocess data の以下のページを翻訳した上で
適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

無料セミナー開催中 クラスキャット主催 人工知能 & ビジネス Web セミナー

人工知能とビジネスをテーマにウェビナー (WEB セミナー) を定期的に開催しています。スケジュールは弊社 公式 Web サイト でご確認頂けます。
  • お住まいの地域に関係なく Web ブラウザからご参加頂けます。事前登録 が必要ですのでご注意ください。
  • Windows PC のブラウザからご参加が可能です。スマートデバイスもご利用可能です。

お問合せ : 本件に関するお問い合わせ先は下記までお願いいたします。

株式会社クラスキャット セールス・マーケティング本部 セールス・インフォメーション
E-Mail:sales-info@classcat.com ; WebSite: https://www.classcat.com/
Facebook: https://www.facebook.com/ClassCatJP/

 

データのロードと前処理 :- CSV データをロードする

このチュートリアルはファイルから CSV データをどのように tf.data.Dataset にロードするかのサンプルを提供します。

このチュートリアルで使用されるデータはタイタニック乗客リストから取られます。モデルは年齢、性別、チケットクラス、そして人が一人で旅行しているか否かのような特質を基に乗局が生き残った尤度を予測します。

 

セットアップ

from __future__ import absolute_import, division, print_function, unicode_literals
import functools

import numpy as np
import tensorflow as tf
TRAIN_DATA_URL = "https://storage.googleapis.com/tf-datasets/titanic/train.csv"
TEST_DATA_URL = "https://storage.googleapis.com/tf-datasets/titanic/eval.csv"

train_file_path = tf.keras.utils.get_file("train.csv", TRAIN_DATA_URL)
test_file_path = tf.keras.utils.get_file("eval.csv", TEST_DATA_URL)
Downloading data from https://storage.googleapis.com/tf-datasets/titanic/train.csv
32768/30874 [===============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tf-datasets/titanic/eval.csv
16384/13049 [=====================================] - 0s 0us/step
# Make numpy values easier to read.
np.set_printoptions(precision=3, suppress=True)

 

データをロードする

始めるにあたり、 それがどのようにフォーマットされているかを見るために CSV ファイルの冒頭を見てみましょう。

!head {train_file_path}
survived,sex,age,n_siblings_spouses,parch,fare,class,deck,embark_town,alone
0,male,22.0,1,0,7.25,Third,unknown,Southampton,n
1,female,38.0,1,0,71.2833,First,C,Cherbourg,n
1,female,26.0,0,0,7.925,Third,unknown,Southampton,y
1,female,35.0,1,0,53.1,First,C,Southampton,n
0,male,28.0,0,0,8.4583,Third,unknown,Queenstown,y
0,male,2.0,3,1,21.075,Third,unknown,Southampton,n
1,female,27.0,0,2,11.1333,Third,unknown,Southampton,n
1,female,14.0,1,0,30.0708,Second,unknown,Cherbourg,n
1,female,4.0,1,1,16.7,Third,G,Southampton,n

pandas を使用してこれをロードし、NumPy 配列を TensorFlow に渡すことができます。ファイルの巨大なセットにスケールアップする必要があるか、あるいは TensorFlow と tf.data (訳注: リンク切れ、こちら を参照) を統合するローダを必要とする場合には、tf.data.experimental.make_csv_dataset 関数を使用してください :

明示的に識別する必要がある唯一のカラムはモデルがが予測することを目的とする値を持つ一つです。

LABEL_COLUMN = 'survived'
LABELS = [0, 1]

今はファイルから CSV データを読みそしてデータセットを作成します。

(完全なドキュメントについては、tf.data.experimental.make_csv_dataset を参照)

def get_dataset(file_path, **kwargs):
  dataset = tf.data.experimental.make_csv_dataset(
      file_path,
      batch_size=5, # Artificially small to make examples easier to show.
      label_name=LABEL_COLUMN,
      na_value="?",
      num_epochs=1,
      ignore_errors=True, 
      **kwargs)
  return dataset

raw_train_data = get_dataset(train_file_path)
raw_test_data = get_dataset(test_file_path)
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow_core/python/data/experimental/ops/readers.py:521: parallel_interleave (from tensorflow.python.data.experimental.ops.interleave_ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use `tf.data.Dataset.interleave(map_func, cycle_length, block_length, num_parallel_calls=tf.data.experimental.AUTOTUNE)` instead. If sloppy execution is desired, use `tf.data.Options.experimental_determinstic`.
def show_batch(dataset):
  for batch, label in dataset.take(1):
    for key, value in batch.items():
      print("{:20s}: {}".format(key,value.numpy()))

データセットの各項目はバッチで、(多くのサンプル、多くのラベル) のタプルとして表わされます。サンプルからのデータは (行ベースの tensor ではなく) カラムベースの tensor で体系化され、それぞれはバッチサイズ (この場合は 5) の多くの要素を持ちます。

これを貴方自身で見ることは役立つかもしれません。

It might help to see this yourself.

show_batch(raw_train_data)
sex                 : [b'female' b'female' b'female' b'male' b'female']
age                 : [28. 28. 50. 16. 28.]
n_siblings_spouses  : [0 0 0 0 2]
parch               : [0 0 1 0 0]
fare                : [ 7.75 79.2  26.    9.5  23.25]
class               : [b'Third' b'First' b'Second' b'Third' b'Third']
deck                : [b'unknown' b'unknown' b'unknown' b'unknown' b'unknown']
embark_town         : [b'Queenstown' b'Cherbourg' b'Southampton' b'Southampton' b'Queenstown']
alone               : [b'y' b'y' b'n' b'y' b'n']

見れるように、CSV のカラムは名前付けられています。dataset コンストラクタはこれらの名前を自動的に選択します。作業しているファイルが最初の行にカラム名を含まない場合、make_csv_dataset 関数で column_names 引数に文字列のリストのそれらを渡します。

CSV_COLUMNS = ['survived', 'sex', 'age', 'n_siblings_spouses', 'parch', 'fare', 'class', 'deck', 'embark_town', 'alone']

temp_dataset = get_dataset(train_file_path, column_names=CSV_COLUMNS)

show_batch(temp_dataset)
sex                 : [b'male' b'male' b'female' b'male' b'male']
age                 : [35. 58. 35.  3. 28.]
n_siblings_spouses  : [0 0 0 1 0]
parch               : [0 0 0 1 0]
fare                : [ 26.     29.7   512.329  18.75   56.496]
class               : [b'Second' b'First' b'First' b'Second' b'Third']
deck                : [b'unknown' b'B' b'unknown' b'unknown' b'unknown']
embark_town         : [b'Southampton' b'Cherbourg' b'Cherbourg' b'Southampton' b'Southampton']
alone               : [b'y' b'y' b'y' b'n' b'y']

このサンプルは利用可能な総てのカラムを使用していきます。dataset から幾つかのカラムを省略する必要がある場合には、使用する予定のカラムだけのリストを作成してそれをコンストラクタの (オプションの) select_columns 引数に渡します。

SELECT_COLUMNS = ['survived', 'age', 'n_siblings_spouses', 'class', 'deck', 'alone']

temp_dataset = get_dataset(train_file_path, select_columns=SELECT_COLUMNS)

show_batch(temp_dataset)
age                 : [34. 28. 26. 31. 18.]
n_siblings_spouses  : [1 1 0 0 1]
class               : [b'Third' b'Third' b'Third' b'First' b'Third']
deck                : [b'unknown' b'unknown' b'unknown' b'C' b'unknown']
alone               : [b'n' b'n' b'y' b'n' b'n']

 

データ前処理

CSV ファイルは様々なデータ型を含むことができます。データをモデルに供給する前に典型的にはそれらの混合型から固定長ベクトルに変換することを望みます。

TensorFlow は一般的入力変換を記述するための組込みシステムを持ちます : tf.feature_column です、詳細は このチュートリアル を見てください。

データを (nltksklearn のような) 好みの任意のツールを使用して前処理して、処理された出力を単に TensorFlow に渡すことができます。

貴方のモデル内で前処理を行なう主要な優位点はモデルをエクスポートするときそれが前処理を含むことです。この方法で生データをモデルに直接渡すことができます。

 

連続型データ

貴方のデータが既に適切な数値フォーマットにあれば、データをモデルに渡す前にそれをベクトルにパックできます :

SELECT_COLUMNS = ['survived', 'age', 'n_siblings_spouses', 'parch', 'fare']
DEFAULTS = [0, 0.0, 0.0, 0.0, 0.0]
temp_dataset = get_dataset(train_file_path, 
                           select_columns=SELECT_COLUMNS,
                           column_defaults = DEFAULTS)

show_batch(temp_dataset)
age                 : [28. 40. 39. 36. 49.]
n_siblings_spouses  : [8. 0. 0. 0. 1.]
parch               : [2. 0. 0. 0. 0.]
fare                : [ 69.55  153.462  24.15   10.5    56.929]
example_batch, labels_batch = next(iter(temp_dataset)) 

ここに総てのカラムをまとめてパックする単純な関数があります :

def pack(features, label):
  return tf.stack(list(features.values()), axis=-1), label

これをデータセットの各要素に適用します :

packed_dataset = temp_dataset.map(pack)

for features, labels in packed_dataset.take(1):
  print(features.numpy())
  print()
  print(labels.numpy())
[[33.     1.     2.    27.75 ]
 [28.     0.     0.    13.   ]
 [28.     0.     0.     7.75 ]
 [30.     1.     0.    24.   ]
 [16.     4.     1.    39.688]]

[1 0 1 0 0]

データ型を混合した場合これらの単純な数値フィールドを分離することを望むかもしれません。tf.feature_column api はこれらを処理できますが、これは何某かのオーバーヘッドを招きますので本当に必要でない場合には回避すべきです。混合されたデータセットに再度切り替えます :

show_batch(raw_train_data)
sex                 : [b'female' b'male' b'female' b'male' b'male']
age                 : [40. 47. 21. 28. 30.]
n_siblings_spouses  : [1 0 0 0 0]
parch               : [0 0 0 0 0]
fare                : [ 9.475 34.021 10.5    0.     7.225]
class               : [b'Third' b'First' b'Second' b'Second' b'Third']
deck                : [b'unknown' b'D' b'unknown' b'unknown' b'unknown']
embark_town         : [b'Southampton' b'Southampton' b'Southampton' b'Southampton' b'Cherbourg']
alone               : [b'n' b'y' b'y' b'y' b'y']
example_batch, labels_batch = next(iter(temp_dataset)) 

そしてより一般的な preprocessor を定義します、これは数値特徴のリストを選択してそれらを単一のカラムにパックします :

class PackNumericFeatures(object):
  def __init__(self, names):
    self.names = names

  def __call__(self, features, labels):
    numeric_freatures = [features.pop(name) for name in self.names]
    numeric_features = [tf.cast(feat, tf.float32) for feat in numeric_freatures]
    numeric_features = tf.stack(numeric_features, axis=-1)
    features['numeric'] = numeric_features

    return features, labels
NUMERIC_FEATURES = ['age','n_siblings_spouses','parch', 'fare']

packed_train_data = raw_train_data.map(
    PackNumericFeatures(NUMERIC_FEATURES))

packed_test_data = raw_test_data.map(
    PackNumericFeatures(NUMERIC_FEATURES))
show_batch(packed_train_data)
sex                 : [b'male' b'female' b'male' b'male' b'male']
class               : [b'Third' b'Second' b'Third' b'Third' b'Second']
deck                : [b'unknown' b'unknown' b'unknown' b'unknown' b'unknown']
embark_town         : [b'Southampton' b'Southampton' b'Southampton' b'Cherbourg' b'Southampton']
alone               : [b'y' b'n' b'y' b'y' b'y']
numeric             : [[21.     0.     0.     8.663]
 [18.     0.     2.    13.   ]
 [21.     0.     0.     8.433]
 [29.     0.     0.     7.896]
 [39.     0.     0.    26.   ]]
example_batch, labels_batch = next(iter(packed_train_data)) 

 

データ正規化

連続型データは常に正規化されるべきです。

import pandas as pd
desc = pd.read_csv(train_file_path)[NUMERIC_FEATURES].describe()
desc

age n_siblings_spouses parch fare
count  627.000000  627.000000  627.000000  627.000000
mean  29.631308  0.545455  0.379585  34.385399
std  12.511818  1.151090  0.792999  54.597730
min  0.750000  0.000000  0.000000  0.000000
25%  23.000000  0.000000  0.000000  7.895800
50%  28.000000  0.000000  0.000000  15.045800
75%  35.000000  1.000000  0.000000  31.387500
max  80.000000  8.000000  5.000000  512.329200

MEAN = np.array(desc.T['mean'])
STD = np.array(desc.T['std'])

def normalize_numeric_data(data, mean, std):
  # Center the data
  return (data-mean)/std

今は数値カラムを作成します。tf.feature_columns.numeric_column API は normalizer_fn 引数を受け取ります、これは各バッチ上で実行されます。

functools.partial を使用して MEAN and STD を normalizer fn にバインドします。

# See what you just created.
normalizer = functools.partial(normalize_numeric_data, mean=MEAN, std=STD)

numeric_column = tf.feature_column.numeric_column('numeric', normalizer_fn=normalizer, shape=[len(NUMERIC_FEATURES)])
numeric_columns = [numeric_column]
numeric_column
NumericColumn(key='numeric', shape=(4,), default_value=None, dtype=tf.float32, normalizer_fn=functools.partial(<function normalize_numeric_data at 0x7f94682a77b8>, mean=array([29.631,  0.545,  0.38 , 34.385]), std=array([12.512,  1.151,  0.793, 54.598])))

モデルを訓練するとき、数値データのこのブロックを選択して中心に置くためにこの特徴カラムを含めます :

example_batch['numeric']
<tf.Tensor: id=550, shape=(5, 4), dtype=float32, numpy=
array([[45.   ,  0.   ,  0.   , 35.5  ],
       [51.   ,  0.   ,  1.   , 61.379],
       [20.5  ,  0.   ,  0.   ,  7.25 ],
       [25.   ,  0.   ,  0.   ,  7.05 ],
       [28.   ,  0.   ,  0.   ,  7.775]], dtype=float32)>
numeric_layer = tf.keras.layers.DenseFeatures(numeric_columns)
numeric_layer(example_batch).numpy()
array([[ 1.228, -0.474, -0.479,  0.02 ],
       [ 1.708, -0.474,  0.782,  0.494],
       [-0.73 , -0.474, -0.479, -0.497],
       [-0.37 , -0.474, -0.479, -0.501],
       [-0.13 , -0.474, -0.479, -0.487]], dtype=float32)

ここで使用されるう正規化に基づく mean は前もって各カラムの mean を知る必要があります。

 

カテゴリカル・データ

CSV データのカラムの幾つかはカテゴリカル・カラムです。つまり、内容はオプションの制限されたセットの一つであるはずです。

各カテゴリカル・カラムのために tf.feature_column.indicator_column を持つコレクションを作成するために tf.feature_column API を使用します。

CATEGORIES = {
    'sex': ['male', 'female'],
    'class' : ['First', 'Second', 'Third'],
    'deck' : ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'],
    'embark_town' : ['Cherbourg', 'Southhampton', 'Queenstown'],
    'alone' : ['y', 'n']
}
categorical_columns = []
for feature, vocab in CATEGORIES.items():
  cat_col = tf.feature_column.categorical_column_with_vocabulary_list(
        key=feature, vocabulary_list=vocab)
  categorical_columns.append(tf.feature_column.indicator_column(cat_col))
# See what you just created.
categorical_columns
[IndicatorColumn(categorical_column=VocabularyListCategoricalColumn(key='sex', vocabulary_list=('male', 'female'), dtype=tf.string, default_value=-1, num_oov_buckets=0)),
 IndicatorColumn(categorical_column=VocabularyListCategoricalColumn(key='embark_town', vocabulary_list=('Cherbourg', 'Southhampton', 'Queenstown'), dtype=tf.string, default_value=-1, num_oov_buckets=0)),
 IndicatorColumn(categorical_column=VocabularyListCategoricalColumn(key='deck', vocabulary_list=('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'), dtype=tf.string, default_value=-1, num_oov_buckets=0)),
 IndicatorColumn(categorical_column=VocabularyListCategoricalColumn(key='alone', vocabulary_list=('y', 'n'), dtype=tf.string, default_value=-1, num_oov_buckets=0)),
 IndicatorColumn(categorical_column=VocabularyListCategoricalColumn(key='class', vocabulary_list=('First', 'Second', 'Third'), dtype=tf.string, default_value=-1, num_oov_buckets=0))]
categorical_layer = tf.keras.layers.DenseFeatures(categorical_columns)
print(categorical_layer(example_batch).numpy()[0])
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow_core/python/feature_column/feature_column_v2.py:4276: IndicatorColumn._variable_shape (from tensorflow.python.feature_column.feature_column_v2) is deprecated and will be removed in a future version.
Instructions for updating:
The old _FeatureColumn APIs are being deprecated. Please use the new FeatureColumn APIs instead.
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow_core/python/feature_column/feature_column_v2.py:4331: VocabularyListCategoricalColumn._num_buckets (from tensorflow.python.feature_column.feature_column_v2) is deprecated and will be removed in a future version.
Instructions for updating:
The old _FeatureColumn APIs are being deprecated. Please use the new FeatureColumn APIs instead.
[1. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0.]

これはモデルを構築するときデータ処理入力層の一部になります。

 

結合された前処理層

2 つの特徴カラムコレクションを追加してそれらを両者の入力型を抽出して前処理する入力層を作成する tf.keras.layers.DenseFeatures に渡します :

preprocessing_layer = tf.keras.layers.DenseFeatures(categorical_columns+numeric_columns)
print(preprocessing_layer(example_batch).numpy()[0])
[ 1.     0.     1.     0.     0.     0.     0.     0.     0.     0.
  0.     0.     0.     0.     0.     0.     0.     0.     1.228 -0.474
 -0.479  0.02   1.     0.   ]

 

モデルを構築する

preprocessing_layer から始めて、tf.keras.Sequential を構築します。

model = tf.keras.Sequential([
  preprocessing_layer,
  tf.keras.layers.Dense(128, activation='relu'),
  tf.keras.layers.Dense(128, activation='relu'),
  tf.keras.layers.Dense(1, activation='sigmoid'),
])

model.compile(
    loss='binary_crossentropy',
    optimizer='adam',
    metrics=['accuracy'])

 

訓練、評価そして予測する

今ではモデルはインスタンス化されて訓練できます。

train_data = packed_train_data.shuffle(500)
test_data = packed_test_data
model.fit(train_data, epochs=20)
Epoch 1/20
126/126 [==============================] - 2s 15ms/step - loss: 0.5042 - accuracy: 0.7544
Epoch 2/20
126/126 [==============================] - 1s 4ms/step - loss: 0.4183 - accuracy: 0.8214
Epoch 3/20
126/126 [==============================] - 1s 4ms/step - loss: 0.3995 - accuracy: 0.8293
Epoch 4/20
126/126 [==============================] - 1s 4ms/step - loss: 0.3890 - accuracy: 0.8373
Epoch 5/20
126/126 [==============================] - 1s 4ms/step - loss: 0.3791 - accuracy: 0.8341
Epoch 6/20
126/126 [==============================] - 1s 4ms/step - loss: 0.3720 - accuracy: 0.8421
Epoch 7/20
126/126 [==============================] - 1s 4ms/step - loss: 0.3577 - accuracy: 0.8437
Epoch 8/20
126/126 [==============================] - 1s 4ms/step - loss: 0.3557 - accuracy: 0.8453
Epoch 9/20
126/126 [==============================] - 1s 4ms/step - loss: 0.3534 - accuracy: 0.8581
Epoch 10/20
126/126 [==============================] - 1s 4ms/step - loss: 0.3424 - accuracy: 0.8485
Epoch 11/20
126/126 [==============================] - 1s 4ms/step - loss: 0.3423 - accuracy: 0.8501
Epoch 12/20
126/126 [==============================] - 1s 4ms/step - loss: 0.3420 - accuracy: 0.8469
Epoch 13/20
126/126 [==============================] - 1s 4ms/step - loss: 0.3353 - accuracy: 0.8533
Epoch 14/20
126/126 [==============================] - 1s 4ms/step - loss: 0.3221 - accuracy: 0.8549
Epoch 15/20
126/126 [==============================] - 1s 4ms/step - loss: 0.3188 - accuracy: 0.8644
Epoch 16/20
126/126 [==============================] - 1s 4ms/step - loss: 0.3272 - accuracy: 0.8644
Epoch 17/20
126/126 [==============================] - 1s 4ms/step - loss: 0.3126 - accuracy: 0.8676
Epoch 18/20
126/126 [==============================] - 1s 4ms/step - loss: 0.3131 - accuracy: 0.8708
Epoch 19/20
126/126 [==============================] - 1s 4ms/step - loss: 0.3107 - accuracy: 0.8708
Epoch 20/20
126/126 [==============================] - 1s 4ms/step - loss: 0.3058 - accuracy: 0.8708

<tensorflow.python.keras.callbacks.History at 0x7f94682d46a0>

ひとたびモデルが訓練されれば、test_data セット上でその精度を確認できます。

test_loss, test_accuracy = model.evaluate(test_data)

print('\n\nTest Loss {}, Test Accuracy {}'.format(test_loss, test_accuracy))
53/53 [==============================] - 0s 9ms/step - loss: 0.4601 - accuracy: 0.8447


Test Loss 0.460110322168132, Test Accuracy 0.8446969985961914

tf.keras.Model.predict を使用してバッチまたはバッチのデータセット上でラベルを推論できます。

predictions = model.predict(test_data)

# Show some results
for prediction, survived in zip(predictions[:10], list(test_data)[0][1][:10]):
  print("Predicted survival: {:.2%}".format(prediction[0]),
        " | Actual outcome: ",
        ("SURVIVED" if bool(survived) else "DIED"))
Predicted survival: 5.19%  | Actual outcome:  DIED
Predicted survival: 6.50%  | Actual outcome:  SURVIVED
Predicted survival: 4.86%  | Actual outcome:  SURVIVED
Predicted survival: 63.66%  | Actual outcome:  DIED
Predicted survival: 85.39%  | Actual outcome:  SURVIVED
 

以上



AI導入支援 #2 ウェビナー

スモールスタートを可能としたAI導入支援   Vol.2
[無料 WEB セミナー] [詳細]
「画像認識 AI PoC スターターパック」の紹介
既に AI 技術を実ビジネスで活用し、成果を上げている日本企業も多く存在しており、競争優位なビジネスを展開しております。
しかしながら AI を導入したくとも PoC (概念実証) だけでも高額な費用がかかり取組めていない企業も少なくないようです。A I導入時には欠かせない PoC を手軽にしかも短期間で認知度を確認可能とするサービの紹介と共に、AI 技術の特性と具体的な導入プロセスに加え運用時のポイントについても解説いたします。
日時:2021年10月13日(水)
会場:WEBセミナー
共催:クラスキャット、日本FLOW(株)
後援:働き方改革推進コンソーシアム
参加費: 無料 (事前登録制)
人工知能開発支援
◆ クラスキャットは 人工知能研究開発支援 サービスを提供しています :
  • テクニカルコンサルティングサービス
  • 実証実験 (プロトタイプ構築)
  • アプリケーションへの実装
  • 人工知能研修サービス
◆ お問合せ先 ◆
(株)クラスキャット
セールス・インフォメーション
E-Mail:sales-info@classcat.com