Login to your account

Username *
Password *
Remember Me

Create an account

Fields marked with an asterisk (*) are required.
Name *
Username *
Password *
Verify password *
Email *
Verify email *
Captcha *
Reload Captcha

Category (en-us)

Category (en-us)

User Rating: 0 / 5

Star InactiveStar InactiveStar InactiveStar InactiveStar Inactive

And Is Another AI Winter Coming?


Many believed an algorithm would transcend humanity with cognitive awareness. Machines would discern and learn tasks without human intervention and replace workers in droves. They quite literally would be able to “think”. Many people even raised the question whether we could have robots for spouses.

But I am not talking about today. What if I told you this idea was widely marketed in the 1960’s, and AI pioneers Jerome Wiesner, Oliver Selfridge, and Claude Shannon insisted this could happen in their near future?

...... read more


Article written by : Thomas Nield


Category (en-us)

User Rating: 2 / 5

Star ActiveStar ActiveStar InactiveStar InactiveStar Inactive



Before we get started, I made a tool (here’s the source) that runs LDA right inside your browser (it’s pretty neat). Be sure to have that open as I go along. All of the emoji I use come from this page. Just select any one emoji and copy it into the input boxes. If you don’t see the emoji, try using Firefox version 50 or greater.

What is LDA?

LDA or latent Dirichlet allocation is a “generative probabilistic model” of a collection of composites made up of parts. In terms of topic modeling, the composites are documents and the parts are words and/or phrases (n-grams). But you could apply LDA to DNA and nucleotides, pizzas and toppings, molecules and atoms, employees and skills, or keyboards and crumbs.

The probabilistic topic model estimated by LDA consists of two tables (matrices). The first table describes the probability or chance of selecting a particular part when sampling a particular topic (category). The second table describes the chance of selecting a particular topic when sampling a particular document or composite.

                                                                                                                        I suddenly have a taste for bacon avocado toast . 
Take the image up above for example. We have four emoji sequences (the composites) and three types of emoji (the parts). The first three documents have 10 of only one type of emoji while the last document has 10 of each.
X marks the bacon.

After we run our emoji composites through LDA, we end up with the probabilistic topic model you see above. The left table has emoji versus topics and the right table has documents versus topics. Each column in the left table and each row in the right table sums to one (allowing for some truncation and precision loss). So if I were to sample (draw an emoji out of a bag) Topic 0, I’d almost certainly get the avocado emoji. If I sampled Document 3, there’s an equal (uniform) chance I’d get either Topic 0, 1, or 2.

The LDA algorithm assumes your composites were generated like so.

  1. Pick your unique set of parts.
  2. Pick how many composites you want.
  3. Pick how many parts you want per each composite (sample from a Poisson distribution).
  4. Pick how many topics (categories) you want.
  5. Pick a number between not zero and positive infinity and call it alpha.
  6. Pick a number between not zero and positive infinity and call it beta.
  7. Build the parts versus the topics table. For each column, draw a sample (spin the wheel) from a Dirichlet distribution (a distribution of distributions) using beta as the input. Each sample will fill out each column in the table, sum to one, and give the probability of each part per topic (column).
  8. Build the composites versus the topics table. For each row, draw a sample from a Dirichlet distribution using alpha as the input. Each sample will fill out each row in the table, sum to one, and give the probability of each topic (column) per composite.
  9. Build the actual composites. For each composite, 1) look up its row in the composites versus topics table, 2) sample a topic based on the probabilities in the row, 3) go to the parts versus topics table, 4) look up the topic sampled, 5) sample a part based on the probabilities in the column, 6) repeat from step 2 until you’ve reached how many parts this composite was set to have.

Now we know this algorithm (or generative procedure/process) is not how say articles are written but this — for better or worse — is the simplified model LDA assumes.

What is the Dirichlet distribution?

Good ol’ matplotlib.

A bit about the Dirichlet distribution before we move on. What you see up above are iterations of taking 1000 samples from a Dirichlet distribution using an increasing alpha value. The Dirichlet distribution takes a number (called alpha in most places) for each topic (or category). In the GIF and for our purposes, every topic is given the same alpha value you see displayed. Each dot represents some distribution or mixture of the three topics like (1.0, 0.0, 0.0) or (0.4, 0.3, 0.3). Remember that each sample has to add up to one.

At low alpha values (less than one), most of the topic distribution samples are in the corners (near the topics). For really low alpha values, it’s likely you’ll end up sampling (1.0, 0.0, 0.0), (0.0, 1.0, 0.0), or (0.0, 0.0, 1.0). This would mean that a document would only ever have one topic if we were building a three topic probabilistic topic model from scratch.

At alpha equal to one, any space on the surface of the triangle (2-simplex) is fair game (uniformly distributed). You could equally likely end up with a sample favoring only one topic, a sample that gives an even mixture of all the topics, or something in between.

For alpha values greater than one, the samples start to congregate to the center. This means that as alpha gets bigger, your samples will more likely be uniform or an even mixture of all the topics.

The GIF demonstrates the sampling of topic mixtures for the documents but the Dirichlet distribution is also assumed the source (the Bayesian prior) for the mixture of parts per topic. Three topics were used as that works well when plotting in three dimensions but typically one shoots for more than three topics depending on the number of documents they have.

If you look at the tool mentioned earlier, you’ll see an alpha and beta slider. These two hyperparameters are required by LDA. The alpha controls the mixture of topics for any given document. Turn it down and the documents will likely have less of a mixture of topics. Turn it up and the documents will likely have more of a mixture of topics. The beta hyperparameter controls the distribution of words per topic. Turn it down and the topics will likely have less words. Turn it up and the topics will likely have more words.

Ideally we want our composites to be made up of only a few topics and our parts to belong to only some of the topics. With this in mind, alpha and beta are typically set below one.

Why use LDA?

If you view the number of topics as number of clusters and the probabilities as the proportion of cluster membership then using LDA is a way of soft clustering your composites and parts. Contrast this with say k-means where each entity can only belong to one cluster. These fuzzy memberships provide a more nuanced way of recommending similar items, finding duplicates, or discovering user profiles/personas.

You could analyze every GitHub repository’s topics/tags and infer themes like native desktop client, back-end web service, single-paged app, or flappy bird clone.

If you choose the number of topics to be less than the documents, using LDA is a way of reducing the dimensionality (the number of rows and columns) of the original composite versus part data set. With the documents now mapped to a lower dimensional latent/hidden topic/category space, you can now apply other machine learning algorithms which will benefit from the smaller number of dimensions. For example, you could run your documents through LDA and then hard cluster them using DBSCAN.

Of course the main reason you’d use latent Dirichlet allocation is to uncover the themes lurking in your data. By using LDA on pizza orders, you might infer pizza topping themes like spicy, salty, savory, and sweet. You could analyze every GitHub repository’s topics/tags and infer themes like native desktop client, back-end web service, single paged app, or flappy bird clone.

How does latent Dirichlet allocation work?

There are a few ways of implementing LDA. Still, like most — if not all — machine learning algorithms, it comes down to estimating one or more parameters. For LDA those parameters are phi and theta (sometimes they’re called something else). Phi is the parts versus topics matrix (or topics versus parts) and theta is the composites versus topics matrix.

Are you more a cat or dog person?

To learn how it works, I’ll make this easy and step through a concrete example. The documents and emoji are shown in the image above. Our hyperparameters are alpha 0.5, beta 0.01, topics 2, and iterations 1. The following manual run through is based on the paper Probabilistic Topic Models, M Steyvers, T Griffiths, Handbook of latent semantic analysis 427 (7), 424–440.

Instead of estimating phi and theta directly, we will estimate the topic assignments for each of the four emoji using the Gibbs sampling algorithm. Once we have the estimated topic assignments, we can then estimate phi and theta.

To start, we need to randomly assign a topic to each emoji. Using a fair coin (sampling from a uniform distribution), we assign to the first cat Topic 0, the second cat Topic 1, the first dog Topic 1, and the second dog Topic 0.

This is our current topic assignment per each emoji.

This is our current emoji versus topic counts.

This our current document versus topic counts.

Now we need to update the topic assignment for the first cat. We subtract one from the emoji versus topic counts for Cat 0, subtract one from the document versus topic counts for Cat 0, calculate the probability of Topic 0 and 1 for Cat 0, flip a biased coin (sample from a categorical distribution), and then update the assignment and counts.

t0 =
(cat emoji with Topic 0 + beta)
(emoji with Topic 0 + unique emoji * beta)
(emoji in Document 0 with Topic 0 + alpha)
(emoji in Document 0 with a topic + number of topics * alpha)
) =
(0 + 0.01)
(1 + 2 * 0.01)
(0 + 0.5)
(1 + 2 * 0.5)
) = 0.0024509803921568627
t1 = ((1 + 0.01) / (2 + 2 * 0.01)) * ((1 + 0.5) / (1 + 2 * 0.5))
= 0.375
p(Cat 0 = Topic 0 | *) = t0 / (t0 + t1) = 0.006493506493506494
p(Cat 0 = Topic 1 | *) = t1 / (t0 + t1) = 0.9935064935064936

After flipping the biased coin, we surprisingly get the same Topic 0 for Cat 0 so our tables before updating Cat 0 remain the same.

Next we do for Cat 1 what we did for Cat 0. After the flipping the biased coin, we get Topic 0 so now our tables look like so.

This is our current topic assignment per each emoji.

This is our current emoji versus topic counts.

This our current document versus topic counts.

What we did for the two cat emoji we now do for the dog emoji. After flipping the biased coins, we end up assigning Topic 1 to Dog 0 and Topic 1 to Dog 1.

This is our current topic assignment per each emoji.

This is our current emoji versus topic counts.

This our current document versus topic counts.

Since we’ve completed one iteration, we are now done with the Gibbs sampling algorithm and we have our topic assignment estimates.

To estimate phi, we use the following equation for each row-column cell in the emoji versus topic count matrix.

And for estimating theta we use the following equation for each row-column cell in the document versus topic count matrix.

Stylish styling brought to you by Bulma.

At long last we are done. The image above contains the estimated phi and theta parameters of our probabilistic topic model.

If you enjoyed this article you might also like Boost your skills, how to easily write K-Meansk-Nearest Neighbors from Scratch, and Let’s make a Linear Regression Calculator with PureScript.

How have you used LDA? Do you prefer Gensim or MALLET? How cool would it be if Elasticsearch did topic modeling out of the box? Let me know in the responses below. Thanks!



Article written by : Lettier (source)

Previous Next

Category (en-us)

User Rating: 0 / 5

Star InactiveStar InactiveStar InactiveStar InactiveStar Inactive

In this tutorial I will showcase the upcoming TensorFlow 2.0 features through the lense of deep reinforcement

learning (DRL) by implementing an advantage actor-critic (A2C) agent to solve the classic CartPole-v0

environment. While the goal is to showcase TensorFlow 2.0, I will do my best to make the DRL aspect

approachable as well, including a brief overview of the field.

In fact since the main focus of the 2.0 release is making developers’ lives easier, it’s a great time to get into DRL

with TensorFlow - our full agent source is under 150 lines! Code is available as a notebook here and online on

Google Colab here.



As TensorFlow 2.0 is still in experimental stage, I recommend installing it in a separate (virtual) environment. I

prefer Anaconda, so I’ll illustrate with it:

> conda create -n tf2 python=3.6
> source activate tf2
> pip install tf-nightly-2.0-preview # tf-nightly-gpu-2.0-preview for GPU version

Let’s quickly verify that everything works as expected:

>>> import tensorflow as tf
>>> print(tf.__version__)
>>> print(tf.executing_eagerly())

Don’t worry about the 1.13.x version, just means that it’s an early preview. What’s important to note here is that

we’re in eager mode by default!

>>> print(tf.reduce_sum([1, 2, 3, 4, 5]))
tf.Tensor(15, shape=(), dtype=int32)

If you’re not yet familiar with eager mode, then in essence it means that computation is executed at runtime,

rather than through a pre-compiled graph. You can find a good overview in the TensorFlow documentation.


Deep Reinforcement Learning

Generally speaking, reinforcement learning is a high level framework for solving sequential decision making

problems. A RL agent navigates an environmentby taking actions based on some observations,

receiving rewards as a result. Most RL algorithms work by maximizing sum of rewards an agent collects in

trajectory, e.g. during one in-game round.

The output of an RL based algorithm is typically a policy - a function that maps states to actions. A valid

policy can be as simple as a hard-coded no-op action. Stochastic policy is represented as a conditional probability

distribution of actions, given some state.


Actor-Critic Methods

RL algorithms are often grouped based on the objective function they are optimized with. Value-based methods, such

as DQN, work by reducing the error of the expected state-action values.

Policy Gradients methods directly optimize the policy itself by adjusting its parameters, typically via gradient

descent. Calculating gradients fully is usually intractable, so instead they are often estimated via monte-carlo methods.

The most popular approach is a hybrid of the two: actor-critic methods, where agents policy is optimized

through policy gradients, while value  based method is used as a bootstrap for the expected value estimates.

Deep Actor-Critic Methods

While much of the fundamental RL theory was developed on the tabular cases, modern RL is almost exclusively done

with function approximators, such as artificial neural networks. Specifically, an RL algorithm is considered “deep” if the

policy and value functions are approximated with deep neural networks.


(Asynchronous) Advantage Actor-Critic

Over the years, a number of improvements have been added to address sample efficiency and stability of the

learning process.

First, gradients are weighted with returns: discounted future rewards, which somewhat alleviates the credit

assignment problem, and resolves theoretical issues with infinite timesteps.

Second, an advantage function is used instead of raw returns. Advantage is formed as the difference

between returns and some baseline (e.g. state-action estimate) and can be thought of as a measure of how

good a given action is  compared to some average.

Third, an additional entropy maximization term is used in objective function to ensure agent sufficiently

explores various policies. In essence, entropy measures how random a probability distribution is, maximized

with uniform distribution.

Finally, multiple workers are used in parallel to speed up sample gathering while helping decorrelate

them during training.

Incorporating all of these changes with deep neural networks we arrive at the two of the most popular modern


(asynchronous) advantage actor critic, or A3C/A2C for short. The difference between the two is more

technical than theoretical: as the name suggests, it boils down to how the parallel workers estimate their

gradients and propagate them to the model.


With this I will wrap up our tour of DRL methods as the focus of the blog post is more on the TensorFlow 2.0 features.

Don’t worry if you’re still unsure about the subject, things should become clearer with code examples. If you want to

learn more then one good resource to get started is Spinning Up in Deep RL.


Advantage Actor-Critic with TensorFlow 2.0

Now that we’re more or less on the same page, let’s see what it takes to implement the basis of many modern DRL

algorithms: an actor-critic agent, described in previous section. For simplicity, we won’t implement parallel workers,

though most of the code will have support for it. An interested reader could then use this as an exercise opportunity.

As a testbed we will use the CartPole-v0 environment. Somewhat simplistic, it’s still a great option to get started with. I

always rely on it as a sanity check when implementing RL algorithms.

Policy & Value via Keras Model API

First, let’s create the policy and value estimate NNs under a single model class:

import numpy as np
import tensorflow as tf
import tensorflow.keras.layers as kl

class ProbabilityDistribution(tf.keras.Model):
    def call(self, logits):
        # sample a random categorical action from given logits
        return tf.squeeze(tf.random.categorical(logits, 1), axis=-1)

class Model(tf.keras.Model):
    def __init__(self, num_actions):
        # no tf.get_variable(), just simple Keras API
        self.hidden1 = kl.Dense(128, activation='relu')
        self.hidden2 = kl.Dense(128, activation='relu')
        self.value = kl.Dense(1, name='value')
        # logits are unnormalized log probabilities
        self.logits = kl.Dense(num_actions, name='policy_logits')
        self.dist = ProbabilityDistribution()

    def call(self, inputs):
        # inputs is a numpy array, convert to Tensor
        x = tf.convert_to_tensor(inputs, dtype=tf.float32)
        # separate hidden layers from the same input tensor
        hidden_logs = self.hidden1(x)
        hidden_vals = self.hidden2(x)
        return self.logits(hidden_logs), self.value(hidden_vals)

    def action_value(self, obs):
        # executes call() under the hood
        logits, value = self.predict(obs)
        action = self.dist.predict(logits)
        # a simpler option, will become clear later why we don't use it
        # action = tf.random.categorical(logits, 1)
        return np.squeeze(action, axis=-1), np.squeeze(value, axis=-1)

And let’s verify the model works as expected:

import gym

env = gym.make('CartPole-v0')
model = Model(num_actions=env.action_space.n)

obs = env.reset()
# no feed_dict or tf.Session() needed at all
action, value = model.action_value(obs[None, :])
print(action, value) # [1] [-0.00145713]

Things to note here:

  • Model layers and execution path are defined separately
  • There is no “input” layer, model will accept raw numpy arrays
  • Two computation paths can be defined in one model via functional API
  • A model can contain helper methods such as action sampling
  • In eager mode everything works from raw numpy arrays

Random Agent

Now we can move on to the fun stuff - the A2CAgent class. First, let’s add a test method that runs through a full

episode and returns sum of rewards.

class A2CAgent:
    def __init__(self, model):
        self.model = model

    def test(self, env, render=True):
        obs, done, ep_reward = env.reset(), False, 0
        while not done:
            action, _ = self.model.action_value(obs[None, :])
            obs, reward, done, _ = env.step(action)
            ep_reward += reward
            if render:
        return ep_reward

Let’s see how much our model scores with randomly initialized weights:

agent = A2CAgent(model)
rewards_sum = agent.test(env)
print("%d out of 200" % rewards_sum) # 18 out of 200

Not even close to optimal, time to get to the training part!

Loss / Objective Function

As I’ve described in the DRL overview section, an agent improves its policy through gradient descent based on some loss (objective) function. In actor-critic we train on three objectives: improving policy with advantage weighted gradients plus entropy maximization, and minizing value estimate errors.

import tensorflow.keras.losses as kls
import tensorflow.keras.optimizers as ko

class A2CAgent:
    def __init__(self, model):
        # hyperparameters for loss terms
        self.params = {'value': 0.5, 'entropy': 0.0001}
        self.model = model
            # define separate losses for policy logits and value estimate
            loss=[self._logits_loss, self._value_loss]

    def test(self, env, render=True):
        # unchanged from previous section

    def _value_loss(self, returns, value):
        # value loss is typically MSE between value estimates and returns
        return self.params['value']*kls.mean_squared_error(returns, value)

    def _logits_loss(self, acts_and_advs, logits):
        # a trick to input actions and advantages through same API
        actions, advantages = tf.split(acts_and_advs, 2, axis=-1)
        # polymorphic CE loss function that supports sparse and weighted options
        # from_logits argument ensures transformation into normalized probabilities
        cross_entropy = kls.CategoricalCrossentropy(from_logits=True)
        # policy loss is defined by policy gradients, weighted by advantages
        # note: we only calculate the loss on the actions we've actually taken
        # thus under the hood a sparse version of CE loss will be executed
        actions = tf.cast(actions, tf.int32)
        policy_loss = cross_entropy(actions, logits, sample_weight=advantages)
        # entropy loss can be calculated via CE over itself
        entropy_loss = cross_entropy(logits, logits)
        # here signs are flipped because optimizer minimizes
        return policy_loss - self.params['entropy']*entropy_loss


And we’re done with the objective functions! Note how compact the code is: there’s almost more comment lines than code itself.

Agent Training Loop

Finally, there’s the train loop itself. It’s relatively long, but fairly straightforward:

collect samples, calculate returns and advantages, and train the model on them.

class A2CAgent:
    def __init__(self, model):
        # hyperparameters for loss terms
        self.params = {'value': 0.5, 'entropy': 0.0001, 'gamma': 0.99}
        # unchanged from previous section
   def train(self, env, batch_sz=32, updates=1000):
        # storage helpers for a single batch of data
        actions = np.empty((batch_sz,), dtype=np.int32)
        rewards, dones, values = np.empty((3, batch_sz))
        observations = np.empty((batch_sz,) + env.observation_space.shape)
        # training loop: collect samples, send to optimizer, repeat updates times
        ep_rews = [0.0]
        next_obs = env.reset()
        for update in range(updates):
            for step in range(batch_sz):
                observations[step] = next_obs.copy()
                actions[step], values[step] = self.model.action_value(next_obs[None, :])
                next_obs, rewards[step], dones[step], _ = env.step(actions[step])

                ep_rews[-1] += rewards[step]
                if dones[step]:
                    next_obs = env.reset()

            _, next_value = self.model.action_value(next_obs[None, :])
            returns, advs = self._returns_advantages(rewards, dones, values, next_value)
            # a trick to input actions and advantages through same API
            acts_and_advs = np.concatenate([actions[:, None], advs[:, None]], axis=-1)
            # performs a full training step on the collected batch
            # note: no need to mess around with gradients, Keras API handles it
            losses = self.model.train_on_batch(observations, [acts_and_advs, returns])
        return ep_rews

    def _returns_advantages(self, rewards, dones, values, next_value):
        # next_value is the bootstrap value estimate of a future state (the critic)
        returns = np.append(np.zeros_like(rewards), next_value, axis=-1)
        # returns are calculated as discounted sum of future rewards
        for t in reversed(range(rewards.shape[0])):
            returns[t] = rewards[t] + self.params['gamma'] * returns[t+1] * (1-dones[t])
        returns = returns[:-1]
        # advantages are returns - baseline, value estimates in our case
        advantages = returns - values
        return returns, advantages

    def test(self, env, render=True):
        # unchanged from previous section

    def _value_loss(self, returns, value):
        # unchanged from previous section

    def _logits_loss(self, acts_and_advs, logits):
        # unchanged from previous section

Training & Results

We’re now all set to train our single-worker A2C agent on CartPole-v0! Training process shouldn’t take

longer than a couple of minutes. After training is complete you should see an agent successfully achieve

 the target 200 out of 200 score.

rewards_history = agent.train(env)
print("Finished training, testing...")
print("%d out of 200" % agent.test(env)) # 200 out of 200


In the source code I include some additional helpers that print out running episode rewards and losses, along with

basic plotter for the rewards_history.

Static Computational Graph

With all of this eager mode excitement you might wonder if static graph execution is even possible anymore. Of

course it is! Moreover, it  takes just one additional line to enable it!

with tf.Graph().as_default():
    print(tf.executing_eagerly()) # False

    model = Model(num_actions=env.action_space.n)
    agent = A2CAgent(model)

    rewards_history = agent.train(env)
    print("Finished training, testing...")
    print("%d out of 200" % agent.test(env)) # 200 out of 200

There’s one caveat that during static graph execution we can’t just have Tensors laying around, which is why we needed that trick with

CategoricalDistribution during model definition. In fact, while I was looking for a way to execute in static mode, I discovered one interesting

low level detail about models built through the Keras API…

One More Thing…

Remember when I said TensorFlow runs in eager mode by default, even proving it with a code snippet? Well, I lied!

Kind of.

If you use Keras API to build and manage your models then it will attempt to compile them as static graphs under the

hood. So what you end up getting is the performance of static computational graphs with flexibility of eager


You can check status of your model via the model.run_eagerly flag. You can also force eager mode by setting

this flag to True, though most of the times you probably don’t need to - if Keras detects that there’s no way around

eager mode, it will back off on its own.

To illustrate that it’s indeed running as a static graph here’s a simple benchmark:

# create a 100000 samples batch
env = gym.make('CartPole-v0')
obs = np.repeat(env.reset()[None, :], 100000, axis=0)

Eager Benchmark


model = Model(env.action_space.n)
model.run_eagerly = True

print("Eager Execution:  ", tf.executing_eagerly())
print("Eager Keras Model:", model.run_eagerly)

_ = model(obs)

######## Results #######

Eager Execution:   True
Eager Keras Model: True
CPU times: user 639 ms, sys: 736 ms, total: 1.38 s

Static Benchmark


with tf.Graph().as_default():
    model = Model(env.action_space.n)

    print("Eager Execution:  ", tf.executing_eagerly())
    print("Eager Keras Model:", model.run_eagerly)

    _ = model.predict(obs)

######## Results #######

Eager Execution:   False
Eager Keras Model: False
CPU times: user 793 ms, sys: 79.7 ms, total: 873 ms

Default Benchmark


model = Model(env.action_space.n)

print("Eager Execution:  ", tf.executing_eagerly())
print("Eager Keras Model:", model.run_eagerly)

_ = model.predict(obs)

######## Results #######

Eager Execution:   True
Eager Keras Model: False
CPU times: user 994 ms, sys: 23.1 ms, total: 1.02 s

As you can see eager mode is behind static mode, and by default our model was indeed executing statically, more or

less matching explicit  static graph execution.



Hopefully this has been an illustrative tour of both DRL and the things to come in TensorFlow 2.0. Note that this is

still just a nightly preview build, not even a release candidate. Everything is subject to change and if there’s something

about TensorFlow you especially dislike (or like :) ) , let the developers know!

A lingering question people might have is if TensorFlow is better than PyTorch? Maybe. Maybe not. Both are great

libraries, so it is hard to say one way or the other. If you’re familiar with PyTorch, you probably noticed that

TensorFlow 2.0 not only caught up, but also avoided some of the PyTorch API pitfalls.

In either case what is clear is that this competition has resulted in a net-positive outcome for both camps and I am

excited to see what will become of the frameworks in the future.





 Article écrit par:  Ring Romain ( Source )