March 18, 2023

Хакнуть Tinder

Приветствую, друзья🥷

В этой статье мы поговорим о совершенно необычном применении искусственного интеллекта и покажем, как его использовать для поиска подходящей пары в Tinder.

Можно создать Auto-tinder на основе Tensorflow и Python 3, который будет автоматически лайкать профили в Tinder на основе ваших предпочтений.

Для этого нам нужно сделать последовательно вот эти шаги:

  1. Проанализировать веб-страницы Tinder на предмет внутренних вызовов API, воссоздать их в Postman, и анализировать их содержимое
  2. Создать класс-обертку для API на Python, чтобы дальше использовать Tinder API.
  3. Загрузить изображения пользователей, которые находятся рядом
  4. Создать обычный классификатор для изображений
  5. Разработать препроцессор, использующий Tensorflow API для распознавания объектов
  6. Переобучить нейронную сеть inceptionv3 на классифицированных данных
  7. Применить классификатор вместе с оберткой Tinder API, чтобы они свайпали за нас.

Шаг 1: Анализ API

Вначале узнаем, как приложение Tinder взаимодействует с бэкендом. Для этого переходим на сайт Tinder, открываем Chrome Devtools и изучаем сетевой протокол. У Tinder есть внутренний API, который они используют для связи между фронт-эндом и бэкэндом. Эта конечная точка API возвращает список профилей пользователей, которые находятся поблизости. Данные включают эту информацию:

{
    "meta": {
        "status": 200
    },
    "data": {
        "results": [
            {
                "type": "user",
                "user": {
                 "_id": "4adfwe547s8df64df",
                    "bio": "19y.",
                    "birth_date": "1997-17-06T18:21:44.654Z",
                    "name": "Anna",
                    "photos": [
                        {
                            "id": "879sdfert-lskdföj-8asdf879-987sdflkj",
                            "crop_info": {
                                "user": {
                                    "width_pct": 1,
                                    "x_offset_pct": 0,
                                    "height_pct": 0.8,
                                    "y_offset_pct": 0.08975463
                                },
                                "algo": {
                                    "width_pct": 0.45674357,
                                    "x_offset_pct": 0.984341657,
                                    "height_pct": 0.234165403,
                                    "y_offset_pct": 0.78902343
                                },
                                "processed_by_bullseye": true,
                                "user_customized": false
                            },
                            "url": "https://images-ssl.gotinder.com/4adfwe547s8df64df/original_879sdfert-lskdföj-8asdf879-987sdflkj.jpeg",
                            "processedFiles": [
                                {
                                    "url": "https://images-ssl.gotinder.com/4adfwe547s8df64df/640x800_879sdfert-lskdföj-8asdf879-987sdflkj.jpg",
                                    "height": 800,
                                    "width": 640
                                },
                                {
                                    "url": "https://images-ssl.gotinder.com/4adfwe547s8df64df/320x400_879sdfert-lskdföj-8asdf879-987sdflkj.jpg",
                                    "height": 400,
                                    "width": 320
                                },
                                {
                                    "url": "https://images-ssl.gotinder.com/4adfwe547s8df64df/172x216_879sdfert-lskdföj-8asdf879-987sdflkj.jpg",
                                    "height": 216,
                                    "width": 172
                                },
                                {
                                    "url": "https://images-ssl.gotinder.com/4adfwe547s8df64df/84x106_879sdfert-lskdföj-8asdf879-987sdflkj.jpg",
                                    "height": 106,
                                    "width": 84
                                }
                            ],
                            "last_update_time": "2019-10-03T16:18:30.532Z",
                            "fileName": "879sdfert-lskdföj-8asdf879-987sdflkj.webp",
                            "extension": "jpg,webp",
                            "webp_qf": [
                                75
                            ]
                        }
                    ],
                    "gender": 1,
                    "jobs": [],
                    "schools": [],
                    "show_gender_on_profile": false
                },
                "facebook": {
                    "common_connections": [],
                    "connection_count": 0,
                    "common_interests": []
                },
                "spotify": {
                    "spotify_connected": false
                },
                "distance_mi": 1,
                "content_hash": "slkadjfiuwejsdfuzkejhrsdbfskdzufiuerwer",
                "s_number": 9876540657341,
                "teaser": {
                    "string": ""
                },
                "teasers": [],
                "snap": {
                    "snaps": []
                }
            }
        ]
    }
}

Все изображения являются общедоступными и их может видеть кто угодно. Оригиналы фотографий имеют высокое разрешение и хранятся на публично доступных серверах. Если вы не выберете «show_gender_on_profile», через API все равно можно увидеть ваш пол («gender»: 1, где 1 = женщина, 0 = мужчина).

Анализируя заголовки, можно быстро найти наш личный ключ к API: X-Auth-Token. Скопировав этот токен в Postman, можно увидеть, что мы действительно можем свободно общаться с API Tinder, используя только правильный URL и токен авторизации.

Шаг 2: Обертка для API

Мы будем пользоваться библиотекой Python Requests для взаимодействия с API и создания обертки. Класс Person будет принимать ответ API и позволит взаимодействовать с ним.

Давайте начнем с написания класса. Он должен получать данные от API и сохранять все нужные данные во внутренних переменных. Кроме того, он должен иметь некоторые базовые функции, такие как “like” или “dislike»”, которые делают запрос к API, чтобы мы могли лайкать интересный нам профиль, используя команду “some_person.like ()”.

import datetime 
from geopy.geocoders import Nominatim

TINDER_URL = "https://api.gotinder.com"
geolocator = Nominatim(user_agent="auto-tinder")
PROF_FILE = "./images/unclassified/profiles.txt"

class Person(object):

    def __init__(self, data, api):
        self._api = api

        self.id = data["_id"]
        self.name = data.get("name", "Unknown")

        self.bio = data.get("bio", "")
        self.distance = data.get("distance_mi", 0) / 1.60934

        self.birth_date = datetime.datetime.strptime(data["birth_date"], '%Y-%m-%dT%H:%M:%S.%fZ') if data.get(
            "birth_date", False) else None
        self.gender = ["Male", "Female", "Unknown"][data.get("gender", 2)]

        self.images = list(map(lambda photo: photo["url"], data.get("photos", [])))

        self.jobs = list(
            map(lambda job: {"title": job.get("title", {}).get("name"), "company": job.get("company", {}).get("name")}, data.get("jobs", [])))
        self.schools = list(map(lambda school: school["name"], data.get("schools", [])))

        if data.get("pos", False):
            self.location = geolocator.reverse(f'{data["pos"]["lat"]}, {data["pos"]["lon"]}')


    def __repr__(self):
        return f"{self.id}  -  {self.name} ({self.birth_date.strftime('%d.%m.%Y')})"


    def like(self):
        return self._api.like(self.id)

    def dislike(self):
        return self._api.dislike(self.id)
На самом деле наша API-обертка - это не более, чем причудливый способ вызова Tinder API с использованием нашего класса:
import requests

TINDER_URL = "https://api.gotinder.com"

class tinderAPI():

    def __init__(self, token):
        self._token = token

    def profile(self):
        data = requests.get(TINDER_URL + "/v2/profile?include=account%2Cuser", headers={"X-Auth-Token": self._token}).json()
        return Profile(data["data"], self)

    def matches(self, limit=10):
        data = requests.get(TINDER_URL + f"/v2/matches?count={limit}", headers={"X-Auth-Token": self._token}).json()
        return list(map(lambda match: Person(match["person"], self), data["data"]["matches"]))

    def like(self, user_id):
        data = requests.get(TINDER_URL + f"/like/{user_id}", headers={"X-Auth-Token": self._token}).json()
        return {
            "is_match": data["match"],
            "liked_remaining": data["likes_remaining"]
        }

    def dislike(self, user_id):
        requests.get(TINDER_URL + f"/pass/{user_id}", headers={"X-Auth-Token": self._token}).json()
        return True

    def nearby_persons(self):
        data = requests.get(TINDER_URL + "/v2/recs/core", headers={"X-Auth-Token": self._token}).json()
        return list(map(lambda user: Person(user["user"], self), data["data"]["results"]))
        

Сейчас мы можем использовать API, чтобы находить пользователей поблизости, смотреть их профили и оценивать их. Для этого замените YOUR-API-TOKEN на X-Auth-Token.

if __name__ == "__main__":
    token = "YOUR-API-TOKEN"
    api = tinderAPI(token)

    while True:
        persons = api.nearby_persons()
        for person in persons:
            print(person)
            # person.like()

Шаг 3: Загружаем фотографии

Далее нам нужно автоматически загрузить около 2000 фотографий пользователей поблизости, которые мы будем использовать для обучения нашего ИИ. Во-первых, расширяем наш класс Person функцией, которая позволит нам загружать изображения.

# At the top of auto_tinder.py
PROF_FILE = "./images/unclassified/profiles.txt"

# inside the Person-class
    def download_images(self, folder=".", sleep_max_for=0):
        with open(PROF_FILE, "r") as f:
            lines = f.readlines()
            if self.id in lines:
                return
        with open(PROF_FILE, "a") as f:
            f.write(self.id+"rn")
        index = -1
        for image_url in self.images:
            index += 1
            req = requests.get(image_url, stream=True)
            if req.status_code == 200:
                with open(f"{folder}/{self.id}_{self.name}_{index}.jpeg", "wb") as f:
                    f.write(req.content)
            sleep(random()*sleep_max_for)
            

В код добавлен sleep не случайно. Это нужно для того, чтобы не перегружать Tinder большим количеством фотографий за несколько секунд.

Записываем ID всех пользователей в файл с именем “profiles.txt”. Сначала нужно проверить, есть ли определенный человек в файле, чтобы не включать один профиль дважды. Этим мы гарантируем, что нам не придется классифицировать одних и тех же людей несколько раз. Теперь итерируем по профилям людей, находящихся неподалеку, и загружаем их изображения в папку “unclassified”.

if __name__ == "__main__":
    token = "YOUR-API-TOKEN"
    api = tinderAPI(token)

    while True:
        persons = api.nearby_persons()
        for person in persons:
            person.download_images(folder="./images/unclassified", sleep_max_for=random()*3)
            sleep(random()*10)
        sleep(random()*10)

После этого запускаем скрипт и даем ему поработать несколько часов, чтобы получить нужное количество фотографий.

Шаг 4: Классификация фотографий

На этом этапе будем создавать простенький классификатор полученных фотографий. Он будет проходить циклом по всем фотографиям в нашей папке “unclassified” и открывать их в GUI. Щелкнув правой кнопкой мыши по человеку, мы будем помечать его как непонравившегося, а щелчок левой кнопкой мыши будет значить, что человек нам понравился.

Позже этот выбор будет отображаться в имени файла: 4tz3kjldfj3482.jpg будет переименован в 1_4tz3kjldfj3482.jpg, если мы лайкнем фотографию или 0_4tz3kjldfj3482.jpg в противном случае.

Используем tkinter, чтобы быстро создать этот GUI:

from os import listdir, rename
from os.path import isfile, join
import tkinter as tk
from PIL import ImageTk, Image

IMAGE_FOLDER = "./images/unclassified"

images = [f for f in listdir(IMAGE_FOLDER) if isfile(join(IMAGE_FOLDER, f))]
unclassified_images = filter(lambda image: not (image.startswith("0_") or image.startswith("1_")), images)
current = None

def next_img():
    global current, unclassified_images
    try:
        current = next(unclassified_images)
    except StopIteration:
        root.quit()
    print(current)
    pil_img = Image.open(IMAGE_FOLDER+"/"+current)
    width, height = pil_img.size
    max_height = 1000
    if height > max_height:
        resize_factor = max_height / height
        pil_img = pil_img.resize((int(width*resize_factor), int(height*resize_factor)), resample=Image.LANCZOS)
    img_tk = ImageTk.PhotoImage(pil_img)
    img_label.img = img_tk
    img_label.config(image=img_label.img)

def positive(arg):
    global current
    rename(IMAGE_FOLDER+"/"+current, IMAGE_FOLDER+"/1_"+current)
    next_img()

def negative(arg):
    global current
    rename(IMAGE_FOLDER + "/" + current, IMAGE_FOLDER + "/0_" + current)
    next_img()


if __name__ == "__main__":

    root = tk.Tk()

    img_label = tk.Label(root)
    img_label.pack()
    img_label.bind("<Button-1>", positive)
    img_label.bind("<Button-3>", negative)

    btn = tk.Button(root, text='Next image', command=next_img)

     
     next_img() # load first image

    root.mainloop()

Загружаем все неклассифицированные фотографии в список «unclassified_images», открываем tkinter, передаем ему первое изображение вызвав next_img () и меняем размер, чтобы оно поместилось на экране. Затем мы задаем два щелчка (левой и правой кнопкой мыши) и вызываем функции “positive” и “negative”, которые переименовывают изображения в зависимости от того какой кнопкой мыши был сделан щелчок, а потом показывают следующую фотографию.

Шаг 5: Создаем препроцессор

Для следующего шага нам нужно привести фотографии к виду, который позволит нам классифицировать их. Есть несколько проблем, которым надо уделить внимание:

1. Размер базы данных: наша база относительно мала. Мы имеем дело с плюс-минус двумя тысячами фотографий, что считается очень маленьким количеством для данных такой сложности.

2. Разнообразие данных: фотографии выполнены с разных ракурсов - со спины, иногда на фото есть только лицо, иногда людей на фото нет.

3. Зашумленность данных: на большинстве фотографий есть не только сам человек, но и фон, который может мешать ИИ.

Мы будем решать эти проблемы двумя способами:

1. Преобразовывать фотографии из цветных в черно-белые, чтобы уменьшить объем информации в 3 раза

2. Вырезать часть фотографии, где находится человек

Первый способ очень просто сделать с использованием Pillow, в котором мы можем открыть наше изображение и перевести его в оттенки серого.

Второй - мы реализуем с помощью Tensorflow API для распознавания объектов и сети MobileNet на базе данных COCO.

Наш скрипт для распознавания людей на фото будет состоять из четырех частей:

Часть 1: Открываем предобученную сеть

В репозитории на Github вы найдете файл .pb с предобученной сетью MobileNet.

Давайте откроем его как граф Tensorflow:

import tensorflow as tf

def open_graph():
    detection_graph = tf.Graph()
    with detection_graph.as_default():
        od_graph_def = tf.GraphDef()
        with tf.gfile.GFile('ssd_mobilenet_v1_coco_2017_11_17/frozen_inference_graph.pb', 'rb') as fid:
            serialized_graph = fid.read()
            od_graph_def.ParseFromString(serialized_graph)
            tf.import_graph_def(od_graph_def, name='')
    return detection_graph

Часть 2: Загружаем фотографии

Используем Pillow для работы с изображениями. Так как Tensorflow для работы нужны numpy-массивы, поэтому напишем функцию, которая преобразует изображения в такие массивы:

import numpy as np

def load_image_into_numpy_array(image):
    (im_width, im_height) = image.size
    return np.array(image.getdata()).reshape(
        (im_height, im_width, 3)).astype(np.uint8)

Часть 3: Вызываем API

Эта функция берет изображение и граф Tensorflow, запускает процесс распознавания, а после возвращает всю информацию о типах объектов, ограничивающие рамки для них и оценки.

import numpy as np
from object_detection.utils import ops as utils_ops
import tensorflow as tf

def run_inference_for_single_image(image, sess):
    ops = tf.get_default_graph().get_operations()
    all_tensor_names = {output.name for op in ops for output in op.outputs}
    tensor_dict = {}
    for key in [
        'num_detections', 'detection_boxes', 'detection_scores',
        'detection_classes', 'detection_masks'
    ]:
        tensor_name = key + ':0'
        if tensor_name in all_tensor_names:
            tensor_dict[key] = tf.get_default_graph().get_tensor_by_name(
                tensor_name)
    if 'detection_masks' in tensor_dict:
        # The following processing is only for single image
        detection_boxes = tf.squeeze(tensor_dict['detection_boxes'], [0])
        detection_masks = tf.squeeze(tensor_dict['detection_masks'], [0])
        # Reframe is required to translate mask from box coordinates to image coordinates and fit the image size.
        real_num_detection = tf.cast(tensor_dict['num_detections'][0], tf.int32)
        detection_boxes = tf.slice(detection_boxes, [0, 0], [real_num_detection, -1])
        detection_masks = tf.slice(detection_masks, [0, 0, 0], [real_num_detection, -1, -1])
        detection_masks_reframed = utils_ops.reframe_box_masks_to_image_masks(
            detection_masks, detection_boxes, image.shape[1], image.shape[2])
        detection_masks_reframed = tf.cast(
            tf.greater(detection_masks_reframed, 0.5), tf.uint8)
        # Follow the convention by adding back the batch dimension
        tensor_dict['detection_masks'] = tf.expand_dims(
            detection_masks_reframed, 0)
    image_tensor = tf.get_default_graph().get_tensor_by_name('image_tensor:0')

    # Run inference
    output_dict = sess.run(tensor_dict,
                           feed_dict={image_tensor: image})

    # all outputs are float32 numpy arrays, so convert types as appropriate
    output_dict['num_detections'] = int(output_dict['num_detections'][0])
    output_dict['detection_classes'] = output_dict[
        'detection_classes'][0].astype(np.int64)
    output_dict['detection_boxes'] = output_dict['detection_boxes'][0]
    output_dict['detection_scores'] = output_dict['detection_scores'][0]
    if 'detection_masks' in output_dict:
        output_dict['detection_masks'] = output_dict['detection_masks'][0]
    return output_dict    

Часть 4: Собираем все

Осталось написать функцию, которая сделает все действия последовательно: возьмет путь к фото, откроет его с помощью Pillow, вызовет API для распознавания и обрежет изображение вокруг человека по рамке.

import numpy as np
from PIL import Image

PERSON_CLASS = 1
SCORE_THRESHOLD = 0.5

def get_person(image_path, sess):
    img = Image.open(image_path)
    image_np = load_image_into_numpy_array(img)
    image_np_expanded = np.expand_dims(image_np, axis=0)
    output_dict = run_inference_for_single_image(image_np_expanded, sess)

    persons_coordinates = []
    for i in range(len(output_dict["detection_boxes"])):
        score = output_dict["detection_scores"][i]
        classtype = output_dict["detection_classes"][i]
        if score > SCORE_THRESHOLD and classtype == PERSON_CLASS:
            persons_coordinates.append(output_dict["detection_boxes"][i])

    w, h = img.size
    for person_coordinate in persons_coordinates:
        cropped_img = img.crop((
            int(w * person_coordinate[1]),
            int(h * person_coordinate[0]),
            int(w * person_coordinate[3]),
            int(h * person_coordinate[2]),
        ))
        return cropped_img
    return None 
    

Часть 5: Размещаем фото

Последний шаг - скрипт, который будет перебирать все фото в папке “unclassified”, проверять, есть ли у них закодированная метка, а потом копировать изображение в соответствующую папку.

import os
import person_detector
import tensorflow as tf

IMAGE_FOLDER = "./images/unclassified"
POS_FOLDER = "./images/classified/positive"
NEG_FOLDER = "./images/classified/negative"


if __name__ == "__main__":
    detection_graph = person_detector.open_graph()

    images = [f for f in os.listdir(IMAGE_FOLDER) if os.path.isfile(os.path.join(IMAGE_FOLDER, f))]
    positive_images = filter(lambda image: (image.startswith("1_")), images)
    negative_images = filter(lambda image: (image.startswith("0_")), images)

    with detection_graph.as_default():
        with tf.Session() as sess:

            for pos in positive_images:
                old_filename = IMAGE_FOLDER + "/" + pos
                new_filename = POS_FOLDER + "/" + pos[:-5] + ".jpg"
                if not os.path.isfile(new_filename):
                    img = person_detector.get_person(old_filename, sess)
                    if not img:
                        continue
                    img = img.convert('L')
                    img.save(new_filename, "jpeg")

            for neg in negative_images:
                old_filename = IMAGE_FOLDER + "/" + neg
                new_filename = NEG_FOLDER + "/" + neg[:-5] + ".jpg"
                if not os.path.isfile(new_filename):
                    img = person_detector.get_person(old_filename, sess)
                    if not img:
                        continue
                    img = img.convert('L')
                    img.save(new_filename, "jpeg") 
                    

После запуска все выбранные изображения обрабатываются и перемещаются в соответствующие подпапки в папке “classified”.

Шаг 6: Переобучение inceptionv3

Для переобучения мы будем просто использовать скрипт Tensorflow и модель inceptionv3. Вызываем скрипт в директории вашего проекта со следующими параметрами:

python retrain.py 
--bottleneck_dir=tf/training_data/bottlenec
ks --model_dir=tf/training_data/inception 
--
summaries_dir=tf/training_data/summaries/
basic --
output_graph=tf/training_output/retrained
_graph.pb 
--output_labels=tf/training_output/retraine
d_labels.txt --
image_dir=./images/classified --
how_many_training_steps=50000 --
testing_percentage=20 --
learning_rate=0.001

В результате у нас будет переобученная модель inceptionV3 в файле «tf/training_output/retrained_graph.pb». Теперь мы должны написать класс Classifier, который открывает сессию. В ней передается граф и имеется метод «classify», который получает фото и возвращает словарь с оценками вероятности того, что фото должно быть помечено “positive” или “negative”.

Класс принимает на вход путь к графу и путь к файлу с метками — оба находятся в нашей папке «tf/training_output/». Мы создаем вспомогательные функции для преобразования файла с фото в тензор, который мы сможем подать в наш граф, вспомогательную функцию для загрузки графа и меток, а также важную маленькую функцию, которая закроет граф после того, как мы закончим работу с ним.

import numpy as np
import tensorflow as tf

class Classifier():
    def __init__(self, graph, labels):

        self._graph = self.load_graph(graph)
        self._labels = self.load_labels(labels)

        self._input_operation = self._graph.get_operation_by_name("import/Placeholder")
        self._output_operation = self._graph.get_operation_by_name("import/final_result")

        self._session = tf.Session(graph=self._graph)

    def classify(self, file_name):
        t = self.read_tensor_from_image_file(file_name)

        # Open up a new tensorflow session and run it on the input
        results = self._session.run(self._output_operation.outputs[0], {self._input_operation.outputs[0]: t})
        results = np.squeeze(results)

        # Sort the output predictions by prediction accuracy
        top_k = results.argsort()[-5:][::-1]

        result = {}
        for i in top_k:
            result[self._labels[i]] = results[i]

        # Return sorted result tuples
        return result

    def close(self):
        self._session.close()


    @staticmethod
    def load_graph(model_file):
        graph = tf.Graph()
        graph_def = tf.GraphDef()
        with open(model_file, "rb") as f:
            graph_def.ParseFromString(f.read())
        with graph.as_default():
            tf.import_graph_def(graph_def)
        return graph

    @staticmethod
    def load_labels(label_file):
        label = []
        proto_as_ascii_lines = tf.gfile.GFile(label_file).readlines()
        for l in proto_as_ascii_lines:
            label.append(l.rstrip())
        return label

    @staticmethod
    def read_tensor_from_image_file(file_name,
                                    input_height=299,
                                    input_width=299,
                                    input_mean=0,
                                    input_std=255):
        input_name = "file_reader"
        file_reader = tf.read_file(file_name, input_name)
        image_reader = tf.image.decode_jpeg(
            file_reader, channels=3, name="jpeg_reader")
        float_caster = tf.cast(image_reader, tf.float32)
        dims_expander = tf.expand_dims(float_caster, 0)
        resized = tf.image.resize_bilinear(dims_expander, [input_height, input_width])
        normalized = tf.divide(tf.subtract(resized, [input_mean]), [input_std])
        sess = tf.Session()
        result = sess.run(normalized)
        return result 
        

Шаг 7: Используем скрипты

Сейчас необходимо расширить класс Person с помощью функции «predict_likeliness», которая использует наш классификатор, чтобы проверить следует ли лайкать этого пользователя или нет.

# In the Person class

    def predict_likeliness(self, classifier, sess):
        ratings = []
        for image in self.images:
            req = requests.get(image, stream=True)
            tmp_filename = f"./images/tmp/run.jpg"
            if req.status_code == 200:
                with open(tmp_filename, "wb") as f:
                    f.write(req.content)
            img = person_detector.get_person(tmp_filename, sess)
            if img:
                img = img.convert('L')
                img.save(tmp_filename, "jpeg")
                certainty = classifier.classify(tmp_filename)
                pos = certainty["positive"]
                ratings.append(pos)
        ratings.sort(reverse=True)
        ratings = ratings[:5]
        if len(ratings) == 0:
            return 0.001
        return ratings[0]*0.6 + sum(ratings[1:])/len(ratings[1:])*0.4
        

После проверки будем собирать все в одно целое. Сперва инициализируем Tinder API. Затем мы создадим сессию для нашего классифицирующего графа Tensorflow с использованием переобученной модели и меток. Затем мы получим данные пользователей поблизости и увидим вероятность того, что тот или иной пользователь может нам понравиться.

И несколько дополнений: коэффициент 1.2 для пользователей Tinder как максимальное совпадение;
0,8 - высокая вероятность понравиться (меньше этого значения профили свайпаются вслево). Получился скрипт, который будет работать 2 часа с начала запуска:

from likeliness_classifier import Classifier
import person_detector
import tensorflow as tf
from time import time

if __name__ == "__main__":
    token = "YOUR-API-TOKEN"
    api = tinderAPI(token)

    detection_graph = person_detector.open_graph()
    with detection_graph.as_default():
        with tf.Session() as sess:

            classifier = Classifier(graph="./tf/training_output/retrained_graph.pb",
                                    labels="./tf/training_output/retrained_labels.txt")

            end_time = time() + 60*60*2
            while time() < end_time:
                try:
                    persons = api.nearby_persons()
                    pos_schools = ["Universität Zürich", "University of Zurich", "UZH"]

                    for person in persons:
                        score = person.predict_likeliness(classifier, sess)

                        for school in pos_schools:
                            if school in person.schools:
                                print()
                                score *= 1.2

                        print("-------------------------")
                        print("ID: ", person.id)
                        print("Name: ", person.name)
                        print("Schools: ", person.schools)
                        print("Images: ", person.images)
                        print(score)

                        if score > 0.8:
                            res = person.like()
                            print("LIKE")
                        else:
                            res = person.dislike()
                            print("DISLIKE")
                except Exception:
                    pass

    classifier.close()

Теперь у вас есть инструкция, чтобы повторить эксперимент. Кто попробует, ждем ваши отзывы о работе.


🔥 Ставь реакцию, если понравилась статья

​​​​​​​​​​​​​​⚡️Остались вопросы? Пиши - @golden_hpa