27
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ZOZOAdvent Calendar 2022

Day 9

AIが音楽に変える!「text2music」でツイートから音楽を作ってみよう

Last updated at Posted at 2022-12-08

はじめに

佐藤(@rayuron)です。実務では推薦に関わる仕事をしています。
Stable DiffusionChatGPT を代表に AI が注目されることが多い毎日です。
趣味で音楽制作をしているので、言語を音楽に変換する text2music AI の Mubert に興味を持っていました。ふと、自分の過去のツイートを音楽に変換するとどうなるのか興味が湧いたのでやってみることにします。
趣味では House, Techno, Disco, Electro, Future のようなタグで表されるの音楽を作っているので、今回生成される音楽との関連性が気になるところです。

▼ 本記事のタイトルは ChatGPT という AI チャットボットに決めてもらいました。
image.png

ゴール

自分の過去のツイートから音楽を生成することです

▼ツイートのイメージ

ここで、自分はツイートをあまりしないことに気づきました。
普段ツイートをしないことがネックとなったので今回はリツイートも含めて音楽を作ります。
AI やスポーツについてよく言及しているので、どういう音楽が生成されるのか楽しみです。

流れ

やること + 実現方法については以下です。
日本語の過去のツイートをもとに音楽を生成したいのですが、 Mubert の入力文字列は英語である必要があるため今回は翻訳 AI として DeepL を組み合わせて使うことにしました。

実装

  • Jupyter Lab を使用して実装します。
  • API の申請方法などの参考資料は最後にまとめて掲載しています。
  • 必要に応じて環境にライブラリをインストールしてください。

1.ツイートを取得する(Twitter API)

Twitter API の利用申請が必要です。

Twitter API を使用して自分の過去のツイートを取得します。

Code

keyuser_id には各々取得したものを入れてください。

from requests_oauthlib import OAuth1Session

# Set keys
API_KEY, SECRET_KEY, ACCESS_TOKEN, ACCESS_TOKEN_SECRET = '', '', '', ''
auth = OAuth1Session(API_KEY, SECRET_KEY, ACCESS_TOKEN, ACCESS_TOKEN_SECRET)

# Set params
params ={'max_results': 100}
user_id = ''

# Request
res = auth.get(url=f'https://api.twitter.com/2/users/{user_id}/tweets',params=params)
tweets = json.loads(res.text)
tweets_text = [x['text'] for x in tweets['data']]

▼Output

['関わらせて頂きましたので是非ご覧ください。 https://t.co/oBh4QtEfd9', 'RT @OpenAI: We’ve just launched the DALL·E API so developers can integrate DALL·E directly into their own apps and ...]

2.取得したツイートを英語にする(DeepL API)

DeepL API の利用にはアカウントの作成が必要です。

DeepL API を使用して取得した日本語のツイートを英語化していきます。
今回は DeepL API FREE プランを使用しています。

Code

keyには取得したものを入れてください。

import requests

DEEPL_API_KEY = ''

tweets_text_en = []
for tweet_text in tweets_text:
  params = {
    "auth_key": DEEPL_API_KEY,
    "text": tweet_text,
    "source_lang": 'JA',
    "target_lang": 'EN'
  }
  request = requests.post("https://api-free.deepl.com/v2/translate", data=params)
  result = request.json()
  tweets_text_en.append(result["translations"][0]["text"])

▼Output

['Please take a look at our involvement. https://t.co/oBh4QtEfd9', "RT @OpenAI: We've just launched the DALL-E API so developers can integrate DALL-E directly into their own apps and ...]

3.英語にしたツイートを前処理する(tweet preprocessor)

必要のない URL や記号、絵文字などを削除します。
ツイートの前処理には、ツイート前処理ライブラリ tweet preprocessor を利用することにしました。 pip でインストール可能です。
また、Mubert の入力に配列は渡せないので、結合して一つの文字列にします。

Code
import preprocessor as p
tweets_text_en = [p.clean(t) for t in tweets_text_en]
prompt = ' '.join(tweets_text_en)

▼Output

'Please take a look at our involvement. : We\'ve just launched the DALL-E API so developers can integrate DALL-E directly into their own apps and ...'

4.前処理したツイートを音楽にする(Mubert)

Mubert API の利用にはアカウントの作成が必要です。

Mubert の利用方法について GitHub にノートブックが公開されています。
流れは以下です。

prompt
prompt は入力文字列のことです。今回は前段で結合したツイートです。

'Please take a look at our involvement. : We\'ve just launched the DALL-E API so developers can integrate DALL-E directly into their own apps and ...'

tags
tags は事前定義されており、ジャンルや雰囲気を表すワードです。

['tribal', 'action', 'kids', 'neo-classic', 'run 130', 'pumped', 'jazz / funk', 'ethnic', 'dubtechno', 'reggae', 'acid jazz',...]
  1. 初めに、学習済みの Sentence Transformers(all-MiniLM-L6-v2) を使用して prompt, tags からそれぞれ embeddings を取得します。
  2. 次に、取得した prompt embeddings と tags embeddings のコサイン類似度を計算して類似度の高い tag を N(今回は10) 個取得します。
  3. 最後に、取得した類似度の高い tags を学習済みの text2music のモデルに入力して音楽を生成します。
Code

Setup Environment

import subprocess, time
print("Setting up environment...")
start_time = time.time()
all_process = [
    ['pip', 'install', 'torch==1.12.1+cu113', 'torchvision==0.13.1+cu113', '--extra-index-url', 'https://download.pytorch.org/whl/cu113'],
    ['pip', 'install', '-U', 'sentence-transformers'],
    ['pip', 'install', 'httpx'],
]
for process in all_process:
    running = subprocess.run(process,stdout=subprocess.PIPE).stdout.decode('utf-8')

end_time = time.time()
print(f"Environment set up in {end_time-start_time:.0f} seconds")

Define Mubert methods and pre-compute things

import numpy as np
from sentence_transformers import SentenceTransformer
minilm = SentenceTransformer('all-MiniLM-L6-v2')

mubert_tags_string = 'tribal,action,kids,neo-classic,run 130,pumped,jazz / funk,ethnic,dubtechno,reggae,acid jazz,liquidfunk,funk,witch house,tech house,underground,artists,mystical,disco,sensorium,r&b,agender,psychedelic trance / psytrance,peaceful,run 140,piano,run 160,setting,meditation,christmas,ambient,horror,cinematic,electro house,idm,bass,minimal,underscore,drums,glitchy,beautiful,technology,tribal house,country pop,jazz & funk,documentary,space,classical,valentines,chillstep,experimental,trap,new jack swing,drama,post-rock,tense,corporate,neutral,happy,analog,funky,spiritual,sberzvuk special,chill hop,dramatic,catchy,holidays,fitness 90,optimistic,orchestra,acid techno,energizing,romantic,minimal house,breaks,hyper pop,warm up,dreamy,dark,urban,microfunk,dub,nu disco,vogue,keys,hardcore,aggressive,indie,electro funk,beauty,relaxing,trance,pop,hiphop,soft,acoustic,chillrave / ethno-house,deep techno,angry,dance,fun,dubstep,tropical,latin pop,heroic,world music,inspirational,uplifting,atmosphere,art,epic,advertising,chillout,scary,spooky,slow ballad,saxophone,summer,erotic,jazzy,energy 100,kara mar,xmas,atmospheric,indie pop,hip-hop,yoga,reggaeton,lounge,travel,running,folk,chillrave & ethno-house,detective,darkambient,chill,fantasy,minimal techno,special,night,tropical house,downtempo,lullaby,meditative,upbeat,glitch hop,fitness,neurofunk,sexual,indie rock,future pop,jazz,cyberpunk,melancholic,happy hardcore,family / kids,synths,electric guitar,comedy,psychedelic trance & psytrance,edm,psychedelic rock,calm,zen,bells,podcast,melodic house,ethnic percussion,nature,heavy,bassline,indie dance,techno,drumnbass,synth pop,vaporwave,sad,8-bit,chillgressive,deep,orchestral,futuristic,hardtechno,nostalgic,big room,sci-fi,tutorial,joyful,pads,minimal 170,drill,ethnic 108,amusing,sleepy ambient,psychill,italo disco,lofi,house,acoustic guitar,bassline house,rock,k-pop,synthwave,deep house,electronica,gabber,nightlife,sport & fitness,road trip,celebration,electro,disco house,electronic'
mubert_tags = np.array(mubert_tags_string.split(','))
mubert_tags_embeddings = minilm.encode(mubert_tags)

from IPython.display import Audio, display
import httpx
import json

def get_track_by_tags(tags, pat, duration, maxit=20, autoplay=False, loop=False):
  if loop:
    mode = "loop"
  else:
    mode = "track"
  r = httpx.post('https://api-b2b.mubert.com/v2/RecordTrackTTM', 
      json={
          "method":"RecordTrackTTM",
          "params": {
              "pat": pat, 
              "duration": duration,
              "tags": tags,
              "mode": mode
          }
      })

  rdata = json.loads(r.text)
  assert rdata['status'] == 1, rdata['error']['text']
  trackurl = rdata['data']['tasks'][0]['download_link']

  print('Generating track ', end='')
  for i in range(maxit):
      r = httpx.get(trackurl)
      if r.status_code == 200:
          display(Audio(trackurl, autoplay=autoplay))
          break
      time.sleep(1)
      print('.', end='')

def find_similar(em, embeddings, method='cosine'):
    scores = []
    for ref in embeddings:
        if method == 'cosine': 
            scores.append(1 - np.dot(ref, em)/(np.linalg.norm(ref)*np.linalg.norm(em)))
        if method == 'norm': 
            scores.append(np.linalg.norm(ref - em))
    return np.array(scores), np.argsort(scores)

def get_tags_for_prompts(prompts, top_n=10, debug=False):
    prompts_embeddings = minilm.encode(prompts)
    ret = []
    for i, pe in enumerate(prompts_embeddings):
        scores, idxs = find_similar(pe, mubert_tags_embeddings)
        top_tags = mubert_tags[idxs[:top_n]]
        top_prob = 1 - scores[idxs[:top_n]]
        if debug:
            print(f"Prompt: {prompts[i]}\nTags: {', '.join(top_tags)}\nScores: {top_prob}\n\n\n")
        ret.append((prompts[i], list(top_tags)))
    return ret

Get personal access token in Mubert and define API methods
email には作成したアカウントのメールアドレスを入力してください。
以下の token は GitHub で公開されています。

email = '' #@param {type:"string"}

r = httpx.post('https://api-b2b.mubert.com/v2/GetServiceAccess', 
    json={
        "method":"GetServiceAccess",
        "params": {
            "email": email,
            "license":"ttmmubertlicense#f0acYBenRcfeFpNT4wpYGaTQIyDI4mJGv5MfIhBFz97NXDwDNFHmMRsBSzmGsJwbTpP1A6i07AXcIeAHo5",
            "token":"4951f6428e83172a4f39de05d5b3ab10d58560b8",
            "mode": "loop"
        }
    })

rdata = json.loads(r.text)
assert rdata['status'] == 1, "probably incorrect e-mail"
pat = rdata['data']['pat']
print(f'Got token: {pat}')

Generate some music 🎵
前段の処理で prompt には既に過去のツイートが代入されているので、# prompt = '' をコメントアウトしています。

# prompt = '' #@param {type:"string"}
duration = 60 #@param {type:"number"}
loop = False #@param {type:"boolean"}

def generate_track_by_prompt(prompt, duration, loop=False):
  _, tags = get_tags_for_prompts([prompt,])[0]
  try:
    print(tags)
    get_track_by_tags(tags, pat, duration, autoplay=True, loop=loop)
  except Exception as e:
    print(str(e))
  print('\n')
  
generate_track_by_prompt(prompt, duration, loop)

上のコードを実行してみると、自分の過去のツイートと類似度の高い tags は以下でした。

[
  'sensorium', 'electronica', 'ambient', 'edm', 'future pop', 
  'chillrave / ethno-house', 'gabber', 'italo disco', 'idm', 'electro'
]

これは、、、
主観的ですが、自分が興味のある House, Techno, Disco, Electro, Future と意味の近い tags になっていると思いました。期待大です。

結果

過去のツイートから生成された音楽は以下の様になりました。
実行毎に結果が変わるので 10 回ほど生成して良いと思った2曲をピックアップします。
*アートワークの画像は Stable Diffusion に tags を入れて生成した画像を使用しました。

最高に好きな曲という感じではありませんが、自分が好きなジャンルの曲を生成することができました。しかし、ここまでの曲ができるとは想像していませんでした、、

おわりに

自分の過去のツイートから音楽を生成するという実験をしました。
自分の場合、普段音楽のツイートをしないのでデータ不足を感じていましたが、Mubert で生成された tags で自分の好みが当てられたので感動しました。同時に過去のツイートからその人が好きな音楽ジャンルを当てることができるという可能性を感じました。tags から生成された音楽に関しても、想像していたクオリティーを超えてきたので感動しました。サンプリングして遊んでみようと思います。Twitter のデータを株式市場の予測に使用する研究は見たことがあったのですが、音楽の分野においてもマルチモーダル AI などに役に立ちそうな可能性を感じました。

また、今回開発する上でやりたいことに対して利用可能な API が提供されているのがすごく助かりました。
さらに今回、記事のタイトル決めは AI ですし曲のアートワーク作成も AI です。
AI の民主化が進んできていることを実験と同時に実感しました。

参考資料

27
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
27
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?