Franck Kessie opened the scoring for Milan near the end of half-time while Jordan Veretout equalised for the Giallorossi early in the second half but Ante Rebic scored the winner for the Rossoneri soon after.
The first half was an attacking affair albeit with AC Milan having most of the opportunities. Roma goalkeeper Pau Lopez was active throughout the first 45 minutes and he had kept his team in the game.
The Spaniard denied Zlatan Ibrahimovic, Rebic twice, and Alexis Saelemaekers, but he also gifted a goal to the Rossoneri striker, who failed to scored with an audacious back-heel attempt. Diavolo defender Simon Kjaer also hit the crossbar with a header.
AC Milan goalkeeper Gianluigi Donnarumma also had to make a couple of saves in the first half, denying both Veretout and Lorenzo Pellegrini, while Henrikh Mkhitaryan squandered a great chance with a wayward chip.
Federico Fazio stomped on the foot on Davide Calabria and a penalty was awarded after a VAR review. Kessie sent the ball to his right and beat Pau Lopez despite the Spaniard guessing correctly.
AC Milan left-back Theo Hernandez shot wide after a burst down the middle early in the second half, but Roma equalised when Leonardo Spinazzola cut the ball back for Veretout, who side-footed a thunderous shot into the far corner.
The Rossoneri regained the lead in the 59th minute after a poor clearance from Pau Lopez allowed the away side to quickly work the ball forward and Rebic scored with a quick turn and shot.
Both teams kept creating chances and the respective goalkeepers remained alert for the remainder of the match. Mkhitaryan wanted a penalty after tangling with Theo Hernandez but the Roma midfielder was booked for simulation.
The victory keeps Milan within four points of city rivals and Serie A leaders Inter while Roma drop to fifth in the league table.