[Python] AI 28 Keras学習モデル png画像からの予測

ネットから入手したファッションアイテム画像について予測してみました。

入手した画像を正方形の背景に埋め込み、リサイズ、グレースケールに変換してから学習モデルに供しました。

正解率は67%でした。Tシャツをシャツと予測ミスしていますが、即席の学習モデルではそんなところでしょう。

import tensorflow.keras as keras
from PIL import Image
import numpy as np
import os,glob,re

labels = ['tshirt','trouser','pullover','dress','coat','sandal','shirt','sneaker','bag','boot']

# 画像ファイル名にはラベル(正解)が含まれている(tshirt1.pngなど)
files = glob.glob('/test/*.png')
print(files)

X = list()
y = list()
for file in files:
    image = Image.open(file)
    image = image.resize((28, 28))
    image = image.convert('L')
    data = np.asarray(image)

    label1 = re.search(r'[a-z]+',os.path.split(file)[1].replace('.png',''))
    label2 = label1.group()
    print(label2)
    label3 = labels.index(label2)
    print(label3)

    X.append(data)
    y.append(label3)

X_test = np.array(X)
# 白黒を反転させる
X_test2 = 255 - np.array(X)
print(X_test2)

y_test = np.array(y)
print(y_test)

model = keras.models.load_model('keras-fmnist-model.h5', compile=True)

y_prob = model.predict(X_test2)
print(y_prob)

y_pred = [ i for prob in y_prob for i,p in enumerate(prob) if p == max(prob)]
# predict_classesは廃止予定
# y_pred = model.predict_classes(X_test2)
print(y_pred)

y_pred2 = np.array(labels)[y_pred]
y_answer = np.array(labels)[y_test]

print(f'予測 {y_pred2} 正解 {y_answer}')
--------------------------------------------------

出力の一部
--------------------------------------------------
[[0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00
  0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00 1.0000000e+00]
 [0.0000000e+00 1.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00
  0.0000000e+00 1.0539890e-37 0.0000000e+00 0.0000000e+00 0.0000000e+00]
 [3.0448330e-03 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00
  0.0000000e+00 9.9695516e-01 0.0000000e+00 0.0000000e+00 0.0000000e+00]]
[9 1 6]
予測 ['boot' 'trouser' 'shirt'] 正解 ['boot' 'trouser' 'tshirt']

[Python] AI 27 Keras学習モデル 訓練, 検証, テスト

Fashion-MNISTを使ったKeras学習モデルでテストするところまで作成しました。訓練回数は30回です。テスト結果はグラフの右上に表示しています。

テストの結果データはhistory.historyにtestキーとして追加し、JSONファイルに保存しています。

import tensorflow.keras as keras
from tensorflow.keras import models
from keras.layers import Dense, Flatten
import matplotlib.pyplot as plt
import time,datetime,os,json

def plot_loss_accuracy_graph(history,eval):
	<略>
	return dt_now_str # グラフ作成日時

def main():
	epochs=30

	# データセット取得
	fashion_mnist = keras.datasets.fashion_mnist
	(X_train, y_train), (X_test, y_test) = fashion_mnist.load_data()

	print(f'X_train_full.shape {X_train.shape}')
	print(f'X_test.shape {X_test.shape}')

	X_train = X_train / 255.
	X_test = X_test / 255.

	# モデル作成
	model = models.Sequential()
	model.add(Flatten(input_shape=[28, 28]))
	model.add(Dense(300, activation="relu"))
	model.add(Dense(100, activation="relu"))
	model.add(Dense(10, activation="softmax"))

	print(f'model.layers {model.layers}')

	model.summary()

	model.compile(loss="sparse_categorical_crossentropy",
				optimizer="sgd",
				metrics=["accuracy"])

	# 訓練
	history = model.fit(X_train, y_train, epochs=epochs,
						validation_split=0.1)
	print(f'history.history {history.history}')

	# 検証結果
	val_loss = history.history['val_loss'][-1]
	val_accuracy = history.history['val_accuracy'][-1]
	print('val_loss:', val_loss)
	print('val_accuracy:', val_accuracy)

	# テスト
	test = model.evaluate(X_test, y_test)
	print(f'test {test}')

	# グラフ化
	ret = plot_loss_accuracy_graph(history,test)

	test2 = {"test":test}
	history.history.update(**test2)

	json_file = f'{ret}_history_data.json'
	with open(json_file ,'w' ) as f:
		json.dump(history.history ,f ,ensure_ascii=False ,indent=4)

	model.save(f'{ret}_keras-mnist-model.h5')

if __name__ == "__main__":
	start = time.time()
	main()

[Python] AI 26 Keras学習モデル matplotlib詳細設定

matplotlibの設定箇所が間延びした見た目だったのでスッキリさせました。

グラフ内と枠内にテキスト表示させています。val_accuracyの最後の値をグラフ内表示しています。枠内表示は実行時間とファイル名です。

start = time.time()

def plot_loss_accuracy_graph(history):
	process_time = time.time() - start
	td = datetime.timedelta(seconds = process_time)

	image ='accuracy_loss.png'

	fig = plt.figure(facecolor='#e0ffff')
	fig.subplots_adjust(bottom=0.15,wspace=0.3)

	ax = fig.add_subplot(121, title = 'ACCURACY',xlabel = 'Epochs',ylabel = 'Accuracy')
	ax.plot(history.history['accuracy'],"-o", color="green", label="train_accuracy", linewidth=2)
	ax.plot(history.history['val_accuracy'],"-o",color="black", label="val_accuracy", linewidth=2)
	ax.legend(loc="lower right")
	
	# グラフ内テキスト表示
	ax.text(len(history.history['val_accuracy']) -1, history.history['val_accuracy'][-1]-0.002, '{:.3f}'.format(history.history['val_accuracy'][-1]),verticalalignment='top',horizontalalignment='center')

	ax2 = fig.add_subplot(122, title = 'LOSS',xlabel = 'Epochs',ylabel = 'Loss')
	ax2.plot(history.history['loss'], "-D", color="blue", label="train_loss", linewidth=2)
	ax2.plot(history.history['val_loss'], "-D", color="black", label="val_loss", linewidth=2)
	ax2.legend(loc='upper right')

	# 枠内テキスト表示
	fig.text(0.68, 0.01, os.path.basename(__file__))
	fig.text(0.85, 0.97, str(td)[:11])

	fig.savefig(image)

[Python] AI 25 Keras学習モデル Fashion-MNIST

今回から教本はオライリー本 “scikit-learn, Keras, TensorFlowによる実践機械学習 第2版”になりました。10.2 KerasによるMLPの実装 から読み進めています。

ファッションアイテム画像のデータセットであるFashion-MNISTを使って識別学習させました。

訓練セットの画像データをX_trainと大文字で表記していますが、数学のルールで2次元配列は大文字、1次元配列は小文字になるからだそうです。

新しい教本ではデータセットを訓練セット、テストセット、検証セットの3種類に分けていました。私が目にしたネット情報では検証セットがないケースがほとんどでした。どちらがより適切なのかは不明ですが、今回は教本に従います。

import tensorflow.keras as keras
from tensorflow.keras import models
from keras.layers import Dense, Flatten
import matplotlib.pyplot as plt
import time,datetime,os,json

def plot_loss_accuracy_graph(history,val_accuracy):
	<略>
	return dt_now_str # 戻り値はグラフ作成日時

def main():
	epochs=10

	fashion_mnist = keras.datasets.fashion_mnist
	(X_train_full, y_train_full), (X_test, y_test) = fashion_mnist.load_data()

	print(f'X_train_full.shape {X_train_full.shape}')

	print(f'X_train_full.dtype {X_train_full.dtype}')

	X_valid, X_train = X_train_full[:5000] / 255., X_train_full[5000:] / 255.
	y_valid, y_train = y_train_full[:5000], y_train_full[5000:]
	X_test = X_test / 255.

	model = models.Sequential()
	model.add(Flatten(input_shape=[28, 28])) # 入力層
	model.add(Dense(300, activation="relu")) # Dense隠れ層
	model.add(Dense(100, activation="relu")) # Dense隠れ層
	model.add(Dense(10, activation="softmax")) # Dense出力層

	print(f'model.layers {model.layers}')

	model.summary()

	model.compile(loss="sparse_categorical_crossentropy",
				optimizer="sgd",
				metrics=["accuracy"])

	history = model.fit(X_train, y_train, epochs=epochs,
						validation_data=(X_valid, y_valid))
	print(f'history.history {history.history}')

	# 検証結果
	val_loss = history.history['val_loss'][-1]
	val_accuracy = history.history['val_accuracy'][-1]
	print('val_loss:', val_loss)
	print('val_accuracy:', val_accuracy)

	# 学習データ・グラフ化
	ret = plot_loss_accuracy_graph(history,val_accuracy)

	json_file = f'{ret}_history_data.json'
	with open(json_file ,'w' ) as f:
		json.dump(history.history ,f ,ensure_ascii=False ,indent=4)

	model.save(f'{ret}_keras-mnist-model.h5')

if __name__ == "__main__":
	main()

[Python] AI 24 Keras学習モデル Custom train loop

Kerasのfit(), evaluate()を使わずforループで訓練する Custom train loopという方法で学習させてみました。

TensorFlowでは計算の高速化のためデータ型はfloat32を採用しています。そのためfloat64しか受け付けないjson.dumpを使う前には、float32からfloat64に変換しておく必要があります。

プログラマの習性でコードとしての整合性ばかり見がちなので、深層学習の理論も並行して身に付けるよう心掛けたいです。

import tensorflow as tf
from tensorflow.keras import models
from keras.layers import Dense, Dropout, Flatten
from keras.layers import Conv2D, MaxPooling2D
import matplotlib.pyplot as plt
import time,datetime,os
from stegano import lsb
import numpy as np
import json

def plot_loss_accuracy_graph(data):
	<略>
	return dt_now_str # 戻り値はグラフ作成日時

def create_model():
	model = models.Sequential()

	model.add(Conv2D(32, kernel_size=(3, 3),activation='relu'))
	model.add(Conv2D(64, (3, 3), activation='relu'))
	model.add(MaxPooling2D(pool_size=(2, 2)))
	model.add(Dropout(0.25))
	model.add(Flatten())
	model.add(Dense(128, activation='relu'))
	model.add(Dropout(0.5))
	model.add(Dense(10, activation='softmax'))

	return model

def main():
	(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
	x_train = x_train.astype(np.float32).reshape(60000,28,28,1) / 255.0
	x_test = x_test.astype(np.float32).reshape(10000,28,28,1) / 255.0

	trainset = tf.data.Dataset.from_tensor_slices((x_train, y_train))
	trainset = trainset.shuffle(buffer_size=1024).batch(128)
	print(f'len(trainset) {len(trainset)}')

	testset = tf.data.Dataset.from_tensor_slices((x_test, y_test))
	testset = testset.batch(128)
	print(f'len(testset) {len(testset)}')

	model = create_model()
	loss = tf.keras.losses.SparseCategoricalCrossentropy()
	acc = tf.keras.metrics.SparseCategoricalAccuracy()
	optim = tf.keras.optimizers.Adam()

	@tf.function
	def train_on_batch(x, y):
		with tf.GradientTape() as tape:
			pred = model(x, training=True)
			loss_val = loss(y, pred)
		
		graidents = tape.gradient(loss_val, model.trainable_weights)
		optim.apply_gradients(zip(graidents, model.trainable_weights))
		acc.update_state(y, pred)

		return loss_val

	# 学習
	epochs = 10

	loss_list = list()
	accuracy_list = list()
	val_loss_list = list()
	val_accuracy_list = list()

	data = dict()
	for i in range(epochs):
		acc.reset_states()
		print("Epoch =", i + 1)

		for step, (x, y) in enumerate(trainset):
			loss1 = train_on_batch(x, y)

			if step % 100 == 0:
				print(f"step = {step}, loss = {loss1}, accuracy = {acc.result()}")
			elif step == 468:
				print(f"step = {step}, loss = {loss1}, accuracy = {acc.result()}")
				loss_list.append(loss1.numpy())
				accuracy_list.append(acc.result().numpy())

		acc.reset_states()
		for step, (x, y) in enumerate(testset):
			pred = model(x, training=False)
			loss2 = loss(y, pred)
			acc.update_state(y, pred)

			if step == 78:
				print(f"test step = {step}, loss = {loss2}, test accuracy = {acc.result()}")
				val_loss_list.append(loss2.numpy())
				val_accuracy_list.append(acc.result().numpy())

	# float32からfloat64に変換
	loss_list2 = [float(a) for a in loss_list]
	accuracy_list2 = [float(a) for a in accuracy_list]
	val_loss_list2 = [float(a) for a in val_loss_list]
	val_accuracy_list2 = [float(a) for a in val_accuracy_list]

	# 各リストをdict型にまとめてhistory.historyと同じデータ構成にする
	data1 = {"loss":loss_list2}
	data2 = {"accuracy":accuracy_list2}
	data3 = {"val_loss":val_loss_list2}
	data4 = {"val_accuracy":val_accuracy_list2}

	data.update(**data1,**data2,**data3,**data4)
	print(f'data {data}')

	# 学習データ・グラフ化
	ret = plot_loss_accuracy_graph(data)

	json_file = '{}_history_data.json'.format(ret)
	with open(json_file ,'w' ) as f:
		json.dump(data ,f ,ensure_ascii=False ,indent=4)

	model.save(f'{ret}_keras-mnist-model.h5')

if __name__ == "__main__":
	main()

参考サイト

[Python] AI 23 Keras学習モデルのTensorFlow2への移植

[macOS Catalina 10.15.7, Python 3.9.7]

TensorFlow1で使っていた学習モデル作成コードをTensorFlow2にうまく移植できず困っていたのですが、ほんの些細なところを修正するだけで解決しました。

TensorFlow2ではConv2DとMaxPooling2Dが求めるinput_shapeの次元が異なるため、あえてinput_shapeの指定を外してライブラリ任せにするとうまくいきました。

学習時間は約1分30秒の短縮です。

import tensorflow as tf
from tensorflow.keras import models
from keras.layers import Dense, Dropout, Flatten
from keras.layers import Conv2D, MaxPooling2D
import matplotlib.pyplot as plt
import time,datetime,os
from stegano import lsb
import numpy as np
import json

def plot_loss_accuracy_graph(history):
	<略>
	return dt_now_str # 戻り値はグラフ作成日時

def create_model():
	model = models.Sequential()

	model.add(Conv2D(32, kernel_size=(3, 3),activation='relu')) # input_shape = (28,28,1)を削除
	model.add(Conv2D(64, (3, 3), activation='relu'))
	model.add(MaxPooling2D(pool_size=(2, 2)))
	model.add(Dropout(0.25))
	model.add(Flatten())
	model.add(Dense(128, activation='relu'))
	model.add(Dropout(0.5))
	model.add(Dense(10, activation='softmax'))

	return model

def main():
	# 前処理
	(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
	x_train = x_train.astype(np.float32).reshape(60000,28,28,1) / 255.0
	x_test = x_test.astype(np.float32).reshape(10000,28,28,1) / 255.0

	# 学習モデル作成
	model = create_model()
	loss = tf.keras.losses.SparseCategoricalCrossentropy()
	acc = tf.keras.metrics.SparseCategoricalAccuracy()
	optim = tf.keras.optimizers.Adam()
	model.compile(optimizer=optim, loss=loss, metrics=[acc])

	# 学習
	epochs = 10
	history = model.fit(x_train, y_train, validation_data=(x_test, y_test), epochs=epochs, batch_size=128)
	print(f'history.history {history.history}')

	# 学習データ・グラフ化
	ret = plot_loss_accuracy_graph(history)
	json_file = '{}_history_data.json'.format(ret)

	with open(json_file ,'w' ) as f:
		json.dump(history.history ,f ,ensure_ascii=False ,indent=4)

	# 検証結果
	test_loss = history.history['val_loss'][-1]
	test_accuracy = history.history['val_sparse_categorical_accuracy'][-1]
	print('Test loss:', test_loss)
	print('Test accuracy:', test_accuracy)

	model.save('keras-mnist-model.h5')

if __name__ == "__main__":

	main()

[Python] AI 22 学習モデル作成時の精度等データ保存

[macOS catalina 10.15.7, Python 3.9.7]

学習モデル作成時の精度等データをJSONファイルに保存できるようにしました。

これで学習モデル間の比較が容易になります。前回記事でソースコードのパスや学習時間をグラフ画像に不可視的に埋め込みましたが、同時にこのファイルの要素として入れておくといいでしょう。

学習モデル作成部分はネットから拝借しました。過学習傾向ですが、精度は高めです。

import tensorflow as tf
import json

def plot_loss_accuracy_graph(history):
	<略>
	return dt_now_str # グラフ作成日時の文字列

def create_model():
    model = models.Sequential()
    model.add(layers.Input((784,)))
    model.add(layers.Dense(128, activation="relu"))
    model.add(layers.Dense(64, activation="relu"))
    model.add(layers.Dense(10, activation="softmax"))
    return model

def main():
	# 前処理
	(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
	x_train = x_train.astype(np.float32).reshape(-1, 784) / 255.0
	x_test = x_test.astype(np.float32).reshape(-1, 784) / 255.0
	
	# 学習モデル作成
	model = create_model()
	loss = tf.keras.losses.SparseCategoricalCrossentropy()
	acc = tf.keras.metrics.SparseCategoricalAccuracy()
	optim = tf.keras.optimizers.Adam()
	model.compile(optimizer=optim, loss=loss, metrics=[acc])

	# 学習
	epochs = 10
	
	history = model.fit(x_train, y_train, validation_data=(x_test, y_test), epochs=epochs, batch_size=128)
	print(f'history.history {history.history}')

	# 学習データグラフ化
	ret = plot_loss_accuracy_graph(history)
	print(f'history.history {history.history}')
  
	# データ保存
	json_file = '{}_history_data.json'.format(ret)
	with open(json_file ,'w' ) as f:
		json.dump(history.history ,f ,ensure_ascii=False ,indent=4)

	# 検証結果
	test_loss = history.history['val_loss'][-1]
	test_accuracy = history.history['val_sparse_categorical_accuracy'][-1]
	print('Test loss:', test_loss)
	print('Test accuracy:', test_accuracy)

	model.save('keras-mnist-model.h5')

if __name__ == "__main__":
	start = time.time()

	dt_now = datetime.datetime.now()
	print('処理開始 '+ str(dt_now))

	main()

	# 処理時間算出
	process_time = time.time() - start
	td = datetime.timedelta(seconds = process_time)
	dt_now = datetime.datetime.now()

	print('処理終了 ' + str(td) + ' ' + str(dt_now))

参考サイト

[Python] AI 21 学習モデル・グラフ画像への不可視的テキスト埋め込み

前回の続きです。tips的内容でAI学習とは直接関係ないのですが、一応書いておきます。

steganoライブラリを使って、画像ファイルにソースファイルの絶対パスと学習時間を不可視的に埋め込みました。画像の質については特に変化はありませんでした。

これでCSVへの記録が不要になり、データの散逸を防止できます。

from stegano import lsb

start = time.time()

def plot_loss_accuracy_graph(history):
	process_time = time.time() - start
	td = datetime.timedelta(seconds = process_time)
	dt_now = datetime.datetime.now()
	dt_now_str = dt_now.strftime('%y%m%d%H%M')

	image_loss ='{}_loss.png'.format(dt_now_str)

	fig = plt.figure()
	plt.plot(history.history['loss'], "-D", color="blue", label="train_loss", linewidth=2)
	plt.plot(history.history['val_loss'], "-D", color="black", label="val_loss", linewidth=2)
	plt.title('LOSS')
	fig.text(0.7, 0.95, os.path.basename(__file__))
	fig.text(0.8, 0.03, str(td))
	plt.xlabel('Epochs')
	plt.ylabel('Loss')
	plt.legend(loc='upper right')
	plt.tight_layout()
	fig.savefig(image_loss)

	# ソースファイルの絶対パスと学習時間をリストにして画像に埋め込む
	embed_text = '[' + os.path.abspath(__file__) + ',' + str(td) + ']'
	image_loss2 = image_loss.split('.')[0] + '2.png'
	secret = lsb.hide(image_loss, embed_text)
	secret.save(image_loss2)
  
	# 埋め込んだテキストを確認する
	embed_text_loss = lsb.reveal(image_loss2)
	print('embed_text_loss '+ embed_text_loss)

[Python] AI 20 学習モデル・グラフへの情報追加

[macOS Catalina 10.15.7, Python 3.5.10]

学習モデルを作成した際の精度や誤差の推移に関するグラフにソースファイル名(右上)と学習時間(右下)を追加しました。学習時間については別途CSVに記録しておくと後で使えます。

Jupyter Notebookは使わず、pyenv-virtualenvの仮想環境を作成し、ソースファイルをターミナルにドラッグ&ドロップして実行しています。

import time,datetime,os

start = time.time()

def plot_loss_accuracy_graph(history):
	process_time = time.time() - start
	td = datetime.timedelta(seconds = process_time)
	dt_now = datetime.datetime.now()
	dt_now_str = dt_now.strftime('%y%m%d%H%M')

	image_loss ='{}_loss.png'.format(dt_now_str)
	image_accuracy ='{}_accuracy.png'.format(dt_now_str)

	fig = plt.figure()
	plt.plot(history.history['loss'], "-D", color="blue", label="train_loss", linewidth=2)
	plt.plot(history.history['val_loss'], "-D", color="black", label="val_loss", linewidth=2)
	plt.title('LOSS')
	fig.text(0.7, 0.95, os.path.basename(__file__))
	fig.text(0.8, 0.03, str(td))
	plt.xlabel('Epochs')
	plt.ylabel('Loss')
	plt.legend(loc='upper right')
	plt.tight_layout()
	fig.savefig(image_loss)

	fig2 = plt.figure()
	plt.plot(history.history['acc'],"-o", color="green", label="train_accuracy", linewidth=2)
	plt.plot(history.history['val_acc'],"-o",color="black", label="val_accuracy", linewidth=2)
	plt.title('ACCURACY')
	fig2.text(0.7, 0.95, os.path.basename(__file__))
	fig2.text(0.8, 0.03, str(td))
	plt.xlabel('Epochs')
	plt.ylabel('Accuracy')
	plt.legend(loc="lower right")
	plt.tight_layout()
	fig2.savefig(image_accuracy)

[Python] AI 19 KerasによるMNISTの画像学習 TensorFlow2

[macOS Catalina 10.15.7, Python 3.9.7]

これまでは教本に合わせてTensorFlowについては1で学習を進めてきましたが、最新技術にキャッチアップするためTensorFlow2を使ってみることにしました。

pip list, pyenv versionsは以下の通りです。ライブラリは最新バージョンで揃えました。

ネットではTensorFlow1とTensorFlow2, Kerasとtensorflow.kerasなど最新情報と陳腐化した情報が混在していてカオスな状況になっています。私を含め学習者はこれらの情報を慎重に取捨選択する必要があります。

Package                 Version
----------------------- ---------
absl-py                 0.14.1
astunparse              1.6.3
cachetools              4.2.4
certifi                 2021.10.8
charset-normalizer      2.0.7
clang                   5.0
cycler                  0.10.0
flatbuffers             1.12
gast                    0.4.0
google-auth             1.35.0
google-auth-oauthlib    0.4.6
google-pasta            0.2.0
grpcio                  1.41.0
h5py                    3.1.0
idna                    3.3
Keras                   2.4.3
Keras-Preprocessing     1.1.2
kiwisolver              1.3.2
Markdown                3.3.4
matplotlib              3.4.3
numpy                   1.19.5
oauthlib                3.1.1
opt-einsum              3.3.0
Pillow                  8.3.2
pip                     21.2.3
protobuf                3.18.1
pyasn1                  0.4.8
pyasn1-modules          0.2.8
pyparsing               2.4.7
python-dateutil         2.8.2
PyYAML                  5.4.1
requests                2.26.0
requests-oauthlib       1.3.0
rsa                     4.7.2
scipy                   1.7.1
setuptools              57.4.0
six                     1.15.0
tensorboard             2.6.0
tensorboard-data-server 0.6.1
tensorboard-plugin-wit  1.8.0
tensorflow              2.6.0
tensorflow-estimator    2.6.0
termcolor               1.1.0
torch                   1.9.1
typing-extensions       3.7.4.3
urllib3                 1.26.7
Werkzeug                2.0.2
wheel                   0.37.0
wrapt                   1.12.1
  system
  3.5.10
  3.7.4
  3.8.2
  3.8.3
  3.8.5
  3.8.6
  3.9.0
  3.9.2
  3.9.4
  3.9.5
  3.9.7
  3.9.7/envs/ai_study_3.9.7
* ai_study_3.9.7 (set by /Users/xxx/.python-version)
  anaconda3-2021.05
  jython-2.7.2