<jupyter_start><jupyter_text>Probability Library - distributions module<jupyter_code>import tensorflow_probability as tfp
tfd = tfp.distributions
normal = tfd.Normal(loc=0, scale=1) # standard normal distribution
print(normal) # event shape is empty for univariate distributions
normal.sample(3) # sample from distribution
normal.prob(0.5) # evaluate the probability density function at a point
normal.log_prob(0.5) # evaluate the log of the probability density function at a point 
 # let's look at an example of a distrete probability distribution
bernoulli = tfd.Bernoulli(probs=0.7)
print(bernoulli)
bernoulli = tfd.Bernoulli(logits=0.847) # can use logits instead of probs (logits is the value before applying a sigmoid to convert to prob)
bernoulli.sample(3)
bernoulli.prob(1)
bernoulli.log_prob(1)
batched_bernoulli = tfd.Bernoulli(probs=[0.4, 0.5]) # list of probs will create multiple distributions in one object
print(batched_bernoulli) # batch shape is now 2
batched_bernoulli.batch_shape
batched_bernoulli.sample(3)
batched_bernoulli.prob([1,1]) # need to pass in a list (same for log_prob)
normal = tfd.Normal(loc=0, scale=1)
normal.sample(3)
normal.prob(0)
normal.log_prob(0)
np.log(normal.prob(0))
# plot normal distribution
plt.hist(normal.sample(10000), bins=50, density=True)
plt.show()
exponential = tfd.Exponential(rate=1)
plt.hist(exponential.sample(10000), bins=50, density=True)
plt.show()
bernoulli = tfd.Bernoulli(probs=0.8)
bernoulli.sample(20)
batched_bernoulli = tfd.Bernoulli(probs=[0.1, 0.25, 0.5, 0.75, 0.9])
batched_bernoulli.sample(5)
probs = [[[0.5, 0.5],
          [0.8, 0.3],
          [0.25, 0.75]]]
batched_bernoulli_2D = tfd.Bernoulli(probs=probs)
print(batched_bernoulli_2D)
batched_bernoulli_2D.sample(3)
batched_bernoulli_2D.prob([[[0, 1],
                            [1, 0],
                            [0, 0]]])<jupyter_output><empty_output><jupyter_text>Multivariate distributions<jupyter_code># multivariate Gaussian
mv_normal = tfd.MultivariateNormalDiag(loc=[-1, 0.5], scale_diag=[1, 1.5])
print(mv_normal) # event shape is now 2
mv_normal.event_shape
mv_normal.sample(3)
_ = plt.hist2d(mv_normal.sample(1000)[:,0], mv_normal.sample(1000)[:,1], bins=25)
mv_normal.log_prob([-0.2, 1.8])
batched_mv_normal = tfd.MultivariateNormalDiag(loc=[[-1,0.5],[2,0],[-0.5,1.5]], scale_diag=[[1,1.5],[2,0.5],[1,1]]) # last dim is event size
print(batched_mv_normal) # batch shape = 3 and event shape = 2
batched_mv_normal.sample(2) # shape --> (sample, batch, event)
normal_diag = tfd.MultivariateNormalDiag(loc=[0,1], scale_diag=[1,2])
normal_diag
normal_diag.sample(10)
plt.scatter(normal_diag.sample(10000)[:,0], normal_diag.sample(10000)[:,1], marker='.', alpha=0.05)
plt.axis('equal')
norm_diag_batch = tfd.MultivariateNormalDiag(loc=[[0,0],[0,0],[0,0]], scale_diag=[[1,2],[2,1],[2,2]])
norm_diag_batch
samples = norm_diag_batch.sample(4)
samples
norm_diag_batch.log_prob(samples)
plt_sample_batch = norm_diag_batch.sample(10000)
print(plt_sample_batch.shape)
fig, axes = plt.subplots(1,3, figsize=(15,5), sharex=True, sharey=True)
for i in range(3):
  axes[i].scatter(plt_sample_batch[:,i,0], plt_sample_batch[:,i,1], marker='.', alpha=0.05)
spherical_2d_gaussian = tfd.MultivariateNormalDiag(loc=[0,0], scale_diag=[1,1])
samples = spherical_2d_gaussian.sample(10000)
x1 = samples[:,0]
x2 = samples[:,1]
sns.jointplot(x=x1,y=x2, kind='kde', fill=True, thresh=0, color='orange')<jupyter_output><empty_output><jupyter_text>Previous distributions using MultivariateNormalDiag only had nonzero values in the diagonal of the covariance matrix so they could not appear diagonal on the plot above. Use MultivariateNormalFullTril to make full covariance matrix.  It is better to use this method (using lower-triangular matrix) than definining full covariance matrix since not all matrices work as covariance matrices.  They need to be symmetric and positive semi-definite.  If you define a lower-triangular matrix (L) with all diagonal entries positive, the covariance matrix calculated as LL^T is guarranteed to be a valid covariance matrix.  If you want to use a certain covariance matrix, convert it into lower-triangular matrix with tf.linalg.cholesky()<jupyter_code># scale_tril is a lower-triangular matrix (i.e. all values in upper right corner are 0)
mu = [0,0]
scale_tril = [[1,0],
              [0.6,0.8]]
sigma = tf.matmul(tf.constant(scale_tril), tf.transpose(tf.constant(scale_tril))) # covariance matrix = scale_tril * scale_tril^T
print(sigma) # this is the covariance matrix
nonspherical_2d_gaussian = tfd.MultivariateNormalTriL(loc=mu, scale_tril=scale_tril)
print(nonspherical_2d_gaussian.mean())
print(nonspherical_2d_gaussian.covariance())
samples = nonspherical_2d_gaussian.sample(10000)
x1 = samples[:,0]
x2 = samples[:,1]
sns.jointplot(x=x1,y=x2, kind='kde', fill=True, thresh=0, color='orange')
# covert full covariance matrix into lower-traingular matrix (Cholesky decomposition)
sigma = [[10.,5.],[5.,10.]]
scale_tril = tf.linalg.cholesky(sigma)
scale_tril # can now use this to define a multivariate normal distribution
tf.matmul(scale_tril, tf.transpose(scale_tril)) # confirmation<jupyter_output><empty_output><jupyter_text>Independent distribution<jupyter_code>mv_normal = tfd.MultivariateNormalDiag(loc=[-1,0.5], scale_diag=[1,1.5])
mv_normal
mv_normal.log_prob([-0.2,1.8]) # only one value
batched_normal = tfd.Normal(loc=[-1,0.5], scale=[1,1.5])
batched_normal
batched_normal.log_prob([-0.2,1.8]) # two values
# independent distribution combines individual batched distributions into joint distribution 
# this is like considering individual features in a model (each with their own distriubtion) but consider how they relate to each other
# independent function 'absorbs' batch dimensions into event dimensions
independent_normal = tfd.Independent(batched_normal, reinterpreted_batch_ndims=1) # this makes it equivalent to mv_normal
independent_normal # now batch_shape is empty and event_shape is 2
independent_normal.log_prob([-0.2, 1.8]) # now only one value
batched_normal = tfd.Normal(loc=[[-1,0.5],[0,1],[0.3,-0.1]], scale=[[1,1.5],[0.2,0.8],[2,1]])
batched_normal # batch shape has two dimensions
independent_normal = tfd.Independent(batched_normal, reinterpreted_batch_ndims=1)
independent_normal # since we are only reinterpreting 1 dim, it is always right-most so now batch shape is 3 and event shape is 2
locs = [-1,1]
scales = [0.5, 1]
batch_normal = tfd.Normal(loc=locs, scale=scales)

t = np.linspace(-4,4,10000)
densities = batch_normal.prob(np.repeat(t[..., np.newaxis], 2, axis=1))
sns.lineplot(x=t, y=densities[:,0], label = 'loc={}, scale={}'.format(locs[0], scales[0]))
sns.lineplot(x=t, y=densities[:,1], label = 'loc={}, scale={}'.format(locs[1], scales[1]))
plt.legend()
plt.show()
bivariate_normal = tfd.Independent(batch_normal, reinterpreted_batch_ndims=1)
bivariate_normal
samples = bivariate_normal.sample(10000)
x1 = samples[:,0]
x2 = samples[:,1]
sns.jointplot(x=x1,y=x2, kind='kde', fill=True, color='orange', xlim=[-4,4], ylim=[-4,4])
bivariate_normal = tfd.MultivariateNormalDiag(loc=locs, scale_diag=scales) # equivalent to using Independent
samples = bivariate_normal.sample(10000)
x1 = samples[:,0]
x2 = samples[:,1]
sns.jointplot(x=x1,y=x2, kind='kde', fill=True, color='orange', xlim=[-4,4], ylim=[-4,4])
loc_grid = [[-100, -100],
            [100, 100],
            [0, 0]]
scale_grid = [[1, 10],
              [1, 10],
              [1, 1]]
batch_normal_3by2 = tfd.Normal(loc=loc_grid, scale=scale_grid)
batch_normal_3by2
normal_batch3_event2 = tfd.Independent(batch_normal_3by2) # default is to absorb every batch dim into event except first
normal_batch3_event2
normal_batch3_event2.log_prob(value=[[-10, -10],
                                     [100, 100],
                                     [1, 1]])
normal_batch1_event3by2 = tfd.Independent(batch_normal_3by2, reinterpreted_batch_ndims=2)
normal_batch1_event3by2
normal_batch1_event3by2.log_prob(value=[[-10, -10],
                                        [100, 100],
                                        [1, 1]])<jupyter_output><empty_output><jupyter_text>Naive Bayes Classifier to newsgroups data set<jupyter_code>from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import CountVectorizer
newsgroups_data = fetch_20newsgroups(subset='train')
print(newsgroups_data['DESCR'])
print(newsgroups_data['data'][0])
newsgroups_data['target'][0]
newsgroups_data['target_names'][7]
n_docs = len(newsgroups_data['data'])
count_vectorizer = CountVectorizer(input='content', binary=True, max_df=0.25, min_df=1.01/n_docs)
binary_bag_of_words = count_vectorizer.fit_transform(newsgroups_data['data'])
binary_bag_of_words.shape
inv_vocab = {value:key for key,value in count_vectorizer.vocabulary_.items()}
y = newsgroups_data['target']
n_classes = y.max() + 1
n_words = binary_bag_of_words.shape[1]

# smoothing parameter
alpha = 1e-6

theta = np.zeros([n_classes, n_words]) # this will stor conditional prob p(x|y) for Naive Bayes classifier
for c in range(n_classes):
  class_mask = (y == c)
  N = class_mask.sum() # num of articles in class to make apriori probability p(y)
  theta[c,:] = (binary_bag_of_words[class_mask,:].sum(axis=0) + alpha) / (N + alpha*2) # alpha*2 in denom since it is bernoulli distribution where it can be 2 values

most_probable_word_ix = theta.argmax(axis=1)
for j, ix in enumerate(most_probable_word_ix):
  print('Most probable word in class {} is {}'.format(newsgroups_data['target_names'][j], inv_vocab[ix]))
batched_bernoulli = tfd.Bernoulli(probs=theta) # this allows us to take random samples from a distribution that describes our data
batched_bernoulli
p_x_given_y = tfd.Independent(batched_bernoulli, reinterpreted_batch_ndims=1)
p_x_given_y
sample = p_x_given_y.sample(10)
sample.shape
chosen_class = 15
newsgroups_data['target_names'][chosen_class]
class_sample = sample[:, chosen_class, :]
class_sample
count_vectorizer.inverse_transform(class_sample)<jupyter_output><empty_output><jupyter_text>Broadcasting- prepend 1s to the smaller shape - below, b has shape 1 while a has shape 2 so adds extra dim to b (3 --> 1x3)- check that the axes of both arrays have sizes that are either equal or 1  - below, b is (1x3) and a is (4x1), so in both dims, one of the variables has a value of 1- stretch the arrays in their size-1 axes  - below, b is broadcasted from 1x3 --> 4x3 and a is broadcasted from 4x1 --> 4x3- broadcasting rule for TensorFlow is same as numpy<jupyter_code>a = np.array([[1],
             [2],
             [3],
             [4]])
b = np.array([0,1,2])
a+b
# slow way to define batch of gaussian distributions
batch_normal = tfd.Normal(loc=[0,0,0,1,1,1], scale=[1,10,100,1,10,100])

# faster with broadcasting
loc = [[0.],
       [1.]]
scale = [1., 10., 100.]
faster_batch_normal = tfd.Normal(loc=loc, scale=scale)
faster_batch_normal
faster_batch_normal.loc
faster_batch_normal.scale
sample1 = tf.random.uniform((2,1))
sample2 = tf.random.uniform((1,3))
sample3 = tf.random.uniform((1,1))
# broadcasting works for prob and log_prob too
print(faster_batch_normal.prob(sample1)) # broadcast on second axis
print(faster_batch_normal.prob(sample2)) # broadcast on first axis
print(faster_batch_normal.prob(sample3)) # broadcast on both axes
# multivariate broadcasting
single_mvt_normal = tfd.MultivariateNormalDiag(loc=[0,0], scale_diag=[1, 0.5])
single_mvt_normal
single_mvt_normal.loc
single_mvt_normal.covariance()
# final axis determines event shape - all others are in batch shape
loc = [[0, 0], # 2x2 --> first 2 is batch shape, second 2 is event shape
       [1, 1]]
scale_diag = [1,0.5] # broadcast from 1x2 to 2x2

batch_mvt_normal = tfd.MultivariateNormalDiag(loc=loc, scale_diag=scale_diag)
batch_mvt_normal
batch_mvt_normal.parameters
# tf interprets last dim as event shape and broadcasts the rest
exp = tfd.Exponential(rate=[[1, 1.5, 0.8],[0.3, 0.4, 1.8]])
exp
ind_exp = tfd.Independent(exp)
ind_exp
ind_exp.sample(4)
normal_distributions = tfd.MultivariateNormalDiag(loc=[[0.5,1],[0.1,0],[0,0.2]],
                                                  scale_diag=[[2,3],[1,3],[4,4]])
normal_distributions
normal_distributions.sample(5)
# loc --> (2,2,3)
loc = [[[0.3, 1.5, 1], [0.2, 0.4, 0.6]],
       [[2, 2.3, 8], [1.4, 1, 1.3]]]
# scale_diag --> (3,)
scale_diag = [0.4, 1, 0.7]
# broadcasting will make scale_diag (2,3,3) where last dimension is interpreted as event shape and all others are batch
normal_distributions = tfd.MultivariateNormalDiag(loc=loc, scale_diag=scale_diag)
normal_distributions
ind_normal_distributions = tfd.Independent(normal_distributions, reinterpreted_batch_ndims=1)
ind_normal_distributions
samples = ind_normal_distributions.sample(5)
samples.shape
inp = tf.random.uniform((2,2,3))
ind_normal_distributions.log_prob(inp)
inp = tf.random.uniform((2,3)) # broadcast over batch dim
ind_normal_distributions.log_prob(inp)
inp = tf.random.uniform((9,2,2,3)) # broadcast over samples
ind_normal_distributions.log_prob(inp)
inp = tf.random.uniform((5,1,2,1)) # shape is either equal to batch/event dim or it is one so it can be broadcast
ind_normal_distributions.log_prob(inp)
def get_data(categories):
  newsgroups_train_data = fetch_20newsgroups(subset='train', categories=categories)
  newsgroups_test_data = fetch_20newsgroups(subset='test', categories=categories)

  n_documents = len(newsgroups_train_data['data'])
  count_vectorizer = CountVectorizer(input='content', binary=True, max_df=0.25, min_df=1.01/n_documents)

  train_binary_bag_of_words = count_vectorizer.fit_transform(newsgroups_train_data['data'])
  test_binary_bag_of_words = count_vectorizer.transform(newsgroups_test_data['data'])

  return (train_binary_bag_of_words.todense(), newsgroups_train_data['target']), (test_binary_bag_of_words.todense(), newsgroups_test_data['target'])

def laplace_smoothing(labels, binary_data, n_classes):
  n_words = binary_data.shape[1]
  alpha = 0.1
  theta = np.zeros([n_classes, n_words]) # matrix of conditional probs (prob word given class)
  for c_k in range(n_classes):
    class_mask = (labels == c_k)
    N = class_mask.sum()
    theta[c_k, :] = (binary_data[class_mask, :].sum(axis=0) + alpha) / (N + alpha*2)
  return theta
categories = ['alt.atheism', 'talk.religion.misc', 'comp.graphics', 'sci.space']
(train_data, train_labels), (test_data, test_labels) = get_data(categories)
smoothed_counts = laplace_smoothing(train_labels, train_data, len(categories))<jupyter_output><empty_output><jupyter_text>To make Naive Bayes classifier, we need three functions:1.   Compute class priors2.   build class conditional distributions3.   put it all together and classify our data<jupyter_code>def class_priors(n_classes, labels):
  counts = np.zeros(n_classes)
  for c_k in range(n_classes):
    counts[c_k] = np.sum(np.where(labels==c_k, 1, 0))
  priors = counts / np.sum(counts)
  print('The class priors are {}'.format(priors))
  return priors
priors = class_priors(len(categories), train_labels)
# Bernoulli distributions
# batch shape --> number of classes
# event shape --> number of words
def make_distribution(probs):
  batch_of_bernoullis = tfd.Bernoulli(probs=probs)
  dist = tfd.Independent(batch_of_bernoullis, reinterpreted_batch_ndims=1)
  return dist

tf_dist = make_distribution(smoothed_counts)
tf_dist
def predict_sample(dist, sample, priors):
  cond_probs = dist.log_prob(sample)
  joint_likelihood = tf.add(np.log(priors), cond_probs) # add rather than multiply since these are log_probs
  norm_factor = tf.math.reduce_logsumexp(joint_likelihood, axis=-1, keepdims=True) # compute log normalization factor
  log_prob = joint_likelihood - norm_factor # subtracting log normalization factor in log space is equivalent to dividing to get log prob
  return log_prob
log_probs = predict_sample(tf_dist, test_data[0], priors)
log_probs
from sklearn.metrics import f1_score

probabilites = []
for sample in test_data:
  probabilites.append(tf.exp(predict_sample(tf_dist, sample, priors)))

probabilites = np.asarray(probabilites)
predicted_classes = np.argmax(probabilites, axis=-1)
print('f1 ', f1_score(test_labels, predicted_classes, average='macro'))
# compare my classifier with sklearn's NB classifier (sanity check)
from sklearn.naive_bayes import BernoulliNB

clf = BernoulliNB(alpha=0.1)
clf.fit(train_data, train_labels)
pred = clf.predict(test_data)
print('f1 from sklearn ', f1_score(test_labels, pred, average='macro'))<jupyter_output>f1 from sklearn  0.8496961488207146<jupyter_text>Trainable distributions<jupyter_code>normal = tfd.Normal(loc=tf.Variable(0, name='loc'), scale=1)
normal.trainable_variables
def nll(X_train): # minimize negative log likelihood (loss function) during training
  return -tf.reduce_mean(normal.log_prob(X_train))
@tf.function
def get_loss_and_grads(X_train):
  with tf.GradientTape() as tape:
    tape.watch(normal.trainable_variables)
    loss = nll(X_train)
  grads = tape.gradient(loss, normal.trainable_variables)
  return loss, grads

X_train = tf.random.normal((1000,), mean=21, stddev=2.1)
normal = tfd.Normal(loc=tf.Variable(0, name='loc', dtype=tf.float32), scale=1)

optimizer = tf.keras.optimizers.SGD(learning_rate=0.05)
num_steps = 100
losses = []
means = []
for _ in range(num_steps):
  loss, grads = get_loss_and_grads(X_train)
  optimizer.apply_gradients(zip(grads, normal.trainable_variables))
  losses.append(loss)
  means.append(normal.loc.numpy())
plt.plot(means)
plt.xlabel('epochs')
plt.ylabel('Mean of Normal Distribution')
plt.title('Training Mean of Normal Dist. on Data with Mean 21')
exponential = tfd.Exponential(rate=0.3, name='exp')

plt.hist(exponential.sample(5000), bins=100, density=True)
plt.show()
exp_train = tfd.Exponential(rate=tf.Variable(1., name='rate'), name='exp_train')
exp_train.trainable_variables
def nll(X_train, distribution):
  return -tf.reduce_mean(distribution.log_prob(X_train))
@tf.function
def get_loss_and_grads(X_train, distribution):
  with tf.GradientTape() as tape:
    tape.watch(distribution.trainable_variables)
    loss = nll(X_train, distribution)
    grads = tape.gradient(loss, distribution.trainable_variables)
  return loss, grads
def exponential_dist_optimization(data, distribution):
  train_loss_results = []
  train_rate_results = []
  optimizer = tf.keras.optimizers.SGD(learning_rate=0.05)
  num_steps=10
  for i in range(num_steps):
    loss, grads = get_loss_and_grads(data, distribution)
    optimizer.apply_gradients(zip(grads, distribution.trainable_variables))
    rate_value = distribution.rate.value()
    train_loss_results.append(loss)
    train_rate_results.append(rate_value)
    print('Step {:02d}: Loss: {:.3f}: Rate: {:.3f}'.format(i, loss, rate_value))
  return train_loss_results, train_rate_results
exponential = tfd.Exponential(rate=0.3, name='exp')
exp_train = tfd.Exponential(rate=tf.Variable(1., name='rate'), name='exp_train')

sampled_data = exponential.sample(5000)
train_loss_results, train_rate_results = exponential_dist_optimization(sampled_data, exp_train)
print('Exact rate: ', exponential.rate.numpy())
print('Pred. rate: ', exp_train.rate.numpy())
plt.plot(np.arange(10), [exponential.rate.numpy()]*10, '--k', label='exact value')
plt.plot(train_rate_results, 'k', label='pred. value')
plt.legend()
plt.xlabel('epochs')
plt.ylabel('rate')
# train distributions for the Bernoulli example
def make_distribution_withGT(data, labels, nb_classes):
  class_data = []
  train_vars = []
  distributions = []
  for c in range(nb_classes):
    train_vars.append(tf.Variable(initial_value=np.random.uniform(low=0.01, high=0.1, size=data.shape[-1])))
    distributions.append(tfd.Bernoulli(probs=train_vars[c]))
    class_mask = (labels == c)
    class_data.append(data[class_mask, :])
  
  for c_num in range(0, nb_classes):
    optimizer = tf.keras.optimizers.Adam() # need a different optimizer for each class
    print('\n%-----------------%')
    print('Class ', c_num)
    print('\n%-----------------%')
    for i in range(0, 100):
      loss, grads = get_loss_and_grads(class_data[c_num], distributions[c_num])
      if i%10 == 0:
        print('iter: {}, loss: {}'.format(i, loss))
      optimizer.apply_gradients(zip(grads, distributions[c_num].trainable_variables))
      eta = 1e-3 # don't let probs get to 0 (takes the place of laplace smoothing)
      clipped_probs = tf.clip_by_value(distributions[c_num].trainable_variables, clip_value_max=1, clip_value_min=eta) # make sure values are between 0-1
      train_vars[c_num] = tf.squeeze(clipped_probs)
  
  dist = tfd.Bernoulli(probs=train_vars)
  dist = tfd.Independent(dist, reinterpreted_batch_ndims=1)
  print(dist)
  return dist
categories = ['alt.atheism', 'talk.religion.misc', 'comp.graphics', 'sci.space']
(train_data, train_labels), (test_data, test_labels) = get_data(categories)
smoothed_counts = laplace_smoothing(train_labels, train_data, len(categories))
priors = class_priors(len(categories), train_labels)
tf_dist = make_distribution(smoothed_counts)
GT_dist = make_distribution_withGT(train_data, train_labels, 4)
for dist in [GT_dist, tf_dist]:
  probabilites = []
  for sample in test_data:
    probabilites.append(predict_sample(dist, sample, priors))
  probabilites = np.asarray(probabilites)
  predicted_classes = np.argmax(probabilites, axis=-1)
  print('f1 ', f1_score(test_labels, predicted_classes, average='macro'))<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.12936114488822373, 'fraction_numerical': 0.03339257319240482, 'mean_word_length': 5.6842763363551105, 'pattern_counts': {'":': 0, '<': 32, '<?xml version=': 0, '>': 41, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '8', 'dir': 'jupyter-structured-clean-dedup', 'id': '427933', 'license': 'MIT', 'n_tokens_mistral': 7987, 'n_tokens_neox': 7522, 'n_words': 1670, 'path': 'Probabilistic_Deep_Learning_with_TensorFlow_Week_1.ipynb', 'repo_name': 'bwa6/Deep_Learning_Practice', 'size': '21382'}